├── src ├── main │ ├── resources │ │ ├── kubejs.plugins.txt │ │ ├── pack.mcmeta │ │ ├── META-INF │ │ │ ├── neoforge.mods.toml │ │ │ └── accesstransformer.cfg │ │ └── morejs.mixins.json │ └── java │ │ └── com │ │ └── almostreliable │ │ └── morejs │ │ ├── features │ │ ├── teleport │ │ │ ├── TeleportType.java │ │ │ ├── package-info.java │ │ │ └── EntityTeleportsEventJS.java │ │ ├── enchantment │ │ │ ├── EnchantmentState.java │ │ │ ├── package-info.java │ │ │ ├── EnchantmentMenuExtension.java │ │ │ ├── IsEnchantableEventJS.java │ │ │ ├── EnchantmentTableEventJS.java │ │ │ ├── PlayerEnchantEventJS.java │ │ │ ├── EnchantmentTableServerEventJS.java │ │ │ ├── EnchantmentTableTooltipEventJS.java │ │ │ ├── EnchantmentMenuState.java │ │ │ └── EnchantmentData.java │ │ ├── package-info.java │ │ ├── misc │ │ │ ├── package-info.java │ │ │ ├── ExperiencePlayerEventJS.java │ │ │ └── PiglinPlayerBehaviorEventJS.java │ │ ├── potion │ │ │ ├── package-info.java │ │ │ ├── CustomBrewingFilter.java │ │ │ ├── PotionBrewingFilter.java │ │ │ └── PotionBrewingRegisterEvent.java │ │ ├── villager │ │ │ ├── package-info.java │ │ │ ├── events │ │ │ │ ├── package-info.java │ │ │ │ ├── StartTradingEventJS.java │ │ │ │ ├── PostUpdateOfferEventJS.java │ │ │ │ ├── SingleUpdateOfferEventJS.java │ │ │ │ ├── UpdateOfferEventJS.java │ │ │ │ ├── WandererTradingEventJS.java │ │ │ │ └── VillagerTradingEventJS.java │ │ │ ├── trades │ │ │ │ ├── package-info.java │ │ │ │ ├── SimpleTrade.java │ │ │ │ ├── CustomTrade.java │ │ │ │ ├── StewTrade.java │ │ │ │ ├── EnchantedItemTrade.java │ │ │ │ ├── TreasureMapTrade.java │ │ │ │ ├── TransformableTrade.java │ │ │ │ └── PotionTrade.java │ │ │ ├── TradeTypes.java │ │ │ ├── TradeFilter.java │ │ │ ├── IntRange.java │ │ │ ├── MerchantOfferCodecPatch.java │ │ │ ├── TradeItem.java │ │ │ ├── OfferExtension.java │ │ │ ├── TradingManager.java │ │ │ ├── TradeMatcher.java │ │ │ └── VillagerUtils.java │ │ └── structure │ │ │ ├── package-info.java │ │ │ ├── StructureTemplateAccess.java │ │ │ ├── StructureBlockInfoModification.java │ │ │ ├── StructureLoadEventJS.java │ │ │ ├── EntityInfoWrapper.java │ │ │ ├── PaletteWrapper.java │ │ │ └── StructureAfterPlaceEventJS.java │ │ ├── util │ │ ├── TriConsumer.java │ │ ├── package-info.java │ │ ├── BlockPosFinder.java │ │ ├── ResourceOrTag.java │ │ ├── Utils.java │ │ └── WeightedList.java │ │ ├── Debug.java │ │ ├── package-info.java │ │ ├── core │ │ ├── package-info.java │ │ └── Events.java │ │ ├── mixin │ │ ├── package-info.java │ │ ├── entity │ │ │ ├── package-info.java │ │ │ └── PiglinSpecificSensorMixin.java │ │ ├── villager │ │ │ ├── package-info.java │ │ │ ├── MerchantOfferAccessor.java │ │ │ ├── VillagerMixin.java │ │ │ ├── BasicItemListingMixin.java │ │ │ ├── VillagerTradingManagerMixin.java │ │ │ ├── AbstractVillagerMixin.java │ │ │ ├── MerchantMenuMixin.java │ │ │ ├── WanderingTraderMixin.java │ │ │ ├── MerchantScreenMixin.java │ │ │ ├── MerchantOfferMixin.java │ │ │ └── VillagerTradesMixin.java │ │ ├── enchanting │ │ │ ├── package-info.java │ │ │ └── EnchantmentScreenMixin.java │ │ ├── structure │ │ │ ├── package-info.java │ │ │ ├── StructureTemplateMixin.java │ │ │ ├── StructureManagerMixin.java │ │ │ ├── StructureStartMixin.java │ │ │ └── StructureBlockInfoMixin.java │ │ └── PotionBrewingBuilderAccessor.java │ │ ├── MoreJS.java │ │ ├── Plugin.java │ │ ├── ForgeEventLoaders.java │ │ └── MoreJSBinding.java └── test │ ├── java │ └── testmod │ │ ├── TestUtils.java │ │ ├── TestMod.java │ │ ├── GameTestTemplates.java │ │ ├── package-info.java │ │ ├── mixin │ │ ├── package-info.java │ │ └── ScriptManagerMixin.java │ │ └── tests │ │ ├── package-info.java │ │ └── PotionBrewingTests.java │ └── resources │ ├── data │ └── morejs │ │ └── structure │ │ └── empty_test_structure.nbt │ ├── testmod.mixins.json │ └── META-INF │ └── neoforge.mods.toml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle.kts ├── .gitattributes ├── .gitignore ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── gradle.properties ├── example_scripts ├── enchantment_table.js ├── potion_brewing.js └── trading.js ├── README.md ├── gradlew.bat ├── CHANGELOG.md └── gradlew /src/main/resources/kubejs.plugins.txt: -------------------------------------------------------------------------------- 1 | com.almostreliable.morejs.Plugin 2 | -------------------------------------------------------------------------------- /src/test/java/testmod/TestUtils.java: -------------------------------------------------------------------------------- 1 | package testmod; 2 | 3 | public class TestUtils { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlmostReliable/morejs/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "${modName}", 4 | "pack_format": 8 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/testmod/TestMod.java: -------------------------------------------------------------------------------- 1 | package testmod; 2 | 3 | import net.neoforged.fml.common.Mod; 4 | 5 | @Mod("testmod") 6 | public class TestMod { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/testmod/GameTestTemplates.java: -------------------------------------------------------------------------------- 1 | package testmod; 2 | 3 | public class GameTestTemplates { 4 | public static final String EMPTY = "empty_test_structure"; 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/data/morejs/structure/empty_test_structure.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlmostReliable/morejs/HEAD/src/test/resources/data/morejs/structure/empty_test_structure.nbt -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/teleport/TeleportType.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.teleport; 2 | 3 | public enum TeleportType { 4 | CHORUS_FRUIT, 5 | ENDER_PEARL 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/util/TriConsumer.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.util; 2 | 3 | @FunctionalInterface 4 | public interface TriConsumer { 5 | void accept(T1 t1, T2 t2, T3 t3); 6 | } 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 3 | } 4 | 5 | val modName: String by extra 6 | val minecraftVersion: String by extra 7 | rootProject.name = "$modName-$minecraftVersion" 8 | -------------------------------------------------------------------------------- /src/test/java/testmod/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package testmod; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/Debug.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs; 2 | 3 | import net.neoforged.fml.loading.FMLEnvironment; 4 | 5 | public class Debug { 6 | public static final boolean ENCHANTMENT = !FMLEnvironment.production; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/EnchantmentState.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | public enum EnchantmentState { 4 | IDLE, 5 | STORE_ENCHANTMENTS, 6 | USE_STORED_ENCHANTMENTS, 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/testmod/mixin/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package testmod.mixin; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/test/java/testmod/tests/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package testmod.tests; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase = GRADLE_USER_HOME 2 | distributionPath = wrapper/dists 3 | distributionUrl = https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | zipStoreBase = GRADLE_USER_HOME 5 | zipStorePath = wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/core/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.core; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/util/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.util; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.mixin; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/test/resources/testmod.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "testmod.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | "ScriptManagerMixin" 7 | ], 8 | "injectors": { 9 | "defaultRequire": 1 10 | }, 11 | "minVersion": "0.8" 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/entity/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.mixin.entity; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.bat text eol=crlf 3 | *.patch text eol=lf 4 | *.java text eol=lf 5 | *.gradle text eol=crlf 6 | *.png binary 7 | *.gif binary 8 | *.exe binary 9 | *.dll binary 10 | *.jar binary 11 | *.lzma binary 12 | *.zip binary 13 | *.pyd binary 14 | *.cfg text eol=lf 15 | *.jks binary -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/misc/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.misc; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.mixin.villager; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/potion/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.potion; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/teleport/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.teleport; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.villager; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/enchanting/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.mixin.enchanting; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/structure/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.mixin.structure; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/structure/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.structure; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea/* 15 | !.idea/scopes 16 | 17 | # gradle 18 | build 19 | .gradle 20 | 21 | # other 22 | extra-mods-* 23 | *.log 24 | *.log.gz 25 | eclipse 26 | run 27 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.enchantment; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/events/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.villager.events; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.morejs.features.villager.trades; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/util/BlockPosFinder.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.util; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.world.entity.Entity; 6 | 7 | import javax.annotation.Nullable; 8 | 9 | @FunctionalInterface 10 | public interface BlockPosFinder { 11 | 12 | @Nullable 13 | BlockPos findPosition(ServerLevel level, Entity entity); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/TradeTypes.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | public enum TradeTypes { 4 | DyedArmorForEmeralds, 5 | EnchantBookForEmeralds, 6 | EnchantedItemForEmeralds, 7 | ItemsForEmeralds, 8 | ItemsAndEmeraldsToItems, 9 | EmeraldForItems, 10 | TippedArrowForItemsAndEmeralds, 11 | SuspiciousStewForEmeralds, 12 | TreasureMapForEmeralds, 13 | EmeraldsForVillagerTypeItem, 14 | ForgeBasic, 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/structure/StructureTemplateAccess.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.structure; 2 | 3 | import net.minecraft.core.Vec3i; 4 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 5 | 6 | import java.util.List; 7 | 8 | public interface StructureTemplateAccess { 9 | 10 | 11 | List getPalettes(); 12 | 13 | List getEntities(); 14 | 15 | Vec3i getBorderSize(); 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[2,)" 3 | license = "ARR" 4 | 5 | [[mods]] 6 | modId = "testmod" 7 | version = "0.0.0" 8 | displayName = "Test Mod" 9 | 10 | [[dependencies.testmod]] 11 | modId = "neoforge" 12 | type = "required" 13 | versionRange = "[1,)" 14 | ordering = "NONE" 15 | side = "BOTH" 16 | 17 | [[dependencies.testmod]] 18 | modId = "minecraft" 19 | type = "required" 20 | versionRange = "[1,)" 21 | ordering = "NONE" 22 | side = "BOTH" 23 | 24 | [[mixins]] 25 | config = "testmod.mixins.json" 26 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/MerchantOfferAccessor.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.mojang.serialization.Codec; 4 | import net.minecraft.world.item.trading.MerchantOffer; 5 | import org.spongepowered.asm.mixin.Final; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Mutable; 8 | import org.spongepowered.asm.mixin.gen.Accessor; 9 | 10 | @Mixin(MerchantOffer.class) 11 | public interface MerchantOfferAccessor { 12 | 13 | @Mutable 14 | @Final 15 | @Accessor("CODEC") 16 | static void morejs$setCodec(Codec codec) { 17 | throw new AssertionError(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/MoreJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs; 2 | 3 | import com.almostreliable.morejs.features.villager.MerchantOfferCodecPatch; 4 | import net.neoforged.bus.api.IEventBus; 5 | import net.neoforged.fml.common.Mod; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | @Mod(BuildConfig.MOD_ID) 10 | public class MoreJS { 11 | 12 | public static final Logger LOG = LogManager.getLogger(BuildConfig.MOD_NAME); 13 | public static final String DISABLED_TAG = "morejs$disabled"; 14 | 15 | public MoreJS(IEventBus bus) { 16 | ForgeEventLoaders.load(bus); 17 | MerchantOfferCodecPatch.patch(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/TradeFilter.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import net.minecraft.core.HolderSet; 4 | import net.minecraft.world.entity.npc.VillagerProfession; 5 | import net.minecraft.world.item.crafting.Ingredient; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public record TradeFilter(Optional first, Optional second, Optional output, 11 | Optional firstCount, Optional secondCount, Optional outputCount, 12 | Optional level, Optional> types, 13 | Optional> professions) { 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "1.21" 8 | tags-ignore: 9 | - "**" 10 | paths: 11 | - "gradle/**" 12 | - "**.java" 13 | - "**.kts" 14 | - "**.properties" 15 | - "**/build.yml" 16 | pull_request: 17 | branches: 18 | - "1.21" 19 | paths: 20 | - "gradle/**" 21 | - "**.java" 22 | - "**.kts" 23 | - "**.properties" 24 | - "**/build.yml" 25 | 26 | concurrency: 27 | group: ${{ github.workflow }}-${{ github.ref }} 28 | cancel-in-progress: true 29 | 30 | jobs: 31 | redirect: 32 | uses: AlmostReliable/.github/.github/workflows/build.yml@main 33 | with: 34 | java-distribution: "microsoft" 35 | java-version: "21" 36 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group = com.almostreliable 2 | license = GNU Lesser General Public License v3.0 3 | 4 | # Mod options 5 | modId = morejs 6 | modName = MoreJS 7 | modVersion = 0.14.1 8 | modAuthor = AlmostReliable 9 | modDescription = More KubeJS stuff. 10 | 11 | # Common 12 | minecraftVersion = 1.21 13 | neoforgeVersion = 21.0.167 14 | kubejsVersion = 2100.7.0-build.119 15 | 16 | # AlmostGradle 17 | almostgradle.launchArgs.resizeClient = true 18 | almostgradle.launchArgs.autoWorldJoin = true 19 | 20 | almostgradle.recipeViewers.emi.version = 1.1.12 21 | almostgradle.recipeViewers.emi.minecraftVersion = 1.21 22 | 23 | # Parchment 24 | neoForge.parchment.minecraftVersion = 1.21 25 | neoForge.parchment.mappingsVersion = 2024.07.07 26 | 27 | # Gradle 28 | org.gradle.jvmargs = -Xmx3G 29 | org.gradle.daemon = false 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/EnchantmentMenuExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import net.minecraft.util.RandomSource; 4 | import net.minecraft.world.Container; 5 | import net.minecraft.world.inventory.EnchantmentMenu; 6 | 7 | import java.util.Optional; 8 | 9 | public interface EnchantmentMenuExtension { 10 | 11 | static EnchantmentMenuExtension morejs$cast(EnchantmentMenu menu) { 12 | return (EnchantmentMenuExtension) menu; 13 | } 14 | 15 | Optional morejs$getState(); 16 | 17 | Container morejs$getContainer(); 18 | 19 | int[] morejs$getCosts(); 20 | 21 | int[] morejs$getEnchantmentClues(); 22 | 23 | int[] morejs$getLevelClues(); 24 | 25 | RandomSource morejs$getRandom(); 26 | } 27 | -------------------------------------------------------------------------------- /example_scripts/enchantment_table.js: -------------------------------------------------------------------------------- 1 | MoreJS.playerEnchant((event) => { 2 | if (event.item.id === "minecraft:netherite_pickaxe") { 3 | event.player.tell("You can't enchant Netherite Pickaxes! Oops!"); 4 | event.cancel(); 5 | } 6 | }) 7 | 8 | MoreJS.playerEnchant((event) => { 9 | const player = event.player; 10 | const level = event.requiredLevel; 11 | 12 | player.tell( 13 | `Player enchanted '${event.item.id} on level ${level} and ${event.enchantments.size()}x Enchantments` 14 | ); 15 | player.tell(`Enchantments: ${event.enchantments}`); 16 | }); 17 | 18 | MoreJS.enchantmentTableChanged(event => { 19 | const data = event.get(2) 20 | // data.enchantments.clear() 21 | data.addEnchantment("minecraft:mending", 1) 22 | 23 | data.setRequiredLevel(30) 24 | data.randomClue() 25 | 26 | console.log(data.enchantmentIds) 27 | }) 28 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/VillagerMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.features.villager.events.PostUpdateOfferEventJS; 4 | import net.minecraft.world.entity.npc.AbstractVillager; 5 | import net.minecraft.world.entity.npc.Villager; 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(Villager.class) 12 | public class VillagerMixin { 13 | 14 | @Inject(method = "updateTrades", at = @At(value = "RETURN")) 15 | private void morejs$invokePostUpdateOffer(CallbackInfo ci) { 16 | var self = (AbstractVillager) (Object) this; 17 | PostUpdateOfferEventJS.invoke(self, self.getOffers()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[2,)" 3 | issueTrackerURL = "https://github.com/${githubUser}/${githubRepo}/issues" 4 | license = "${license}" 5 | 6 | [[mods]] 7 | modId = "${modId}" 8 | version = "${version}" 9 | displayName = "${modName}" 10 | authors = "${modAuthor}" 11 | description = '''${modDescription}''' 12 | 13 | [[dependencies.${modId}]] 14 | modId = "neoforge" 15 | type = "required" 16 | versionRange = "[${neoforgeVersion},)" 17 | ordering = "NONE" 18 | side = "BOTH" 19 | 20 | [[dependencies.${modId}]] 21 | modId = "minecraft" 22 | type = "required" 23 | versionRange = "[${minecraftVersion},)" 24 | ordering = "NONE" 25 | side = "BOTH" 26 | 27 | [[dependencies.${modId}]] 28 | modId = "kubejs" 29 | type = "optional" 30 | versionRange = "[${kubejsVersion},)" 31 | ordering = "NONE" 32 | side = "BOTH" 33 | 34 | [[mixins]] 35 | config = "morejs.mixins.json" 36 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/IsEnchantableEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.world.item.ItemStack; 5 | import net.minecraft.world.level.Level; 6 | import org.apache.commons.lang3.mutable.MutableBoolean; 7 | 8 | public class IsEnchantableEventJS extends EnchantmentTableServerEventJS { 9 | private final MutableBoolean enchantable; 10 | 11 | public IsEnchantableEventJS(ItemStack item, ItemStack secondItem, Level level, BlockPos pos, EnchantmentMenuState state, MutableBoolean enchantable) { 12 | super(item, secondItem, level, pos, state.getPlayer(), state); 13 | this.enchantable = enchantable; 14 | } 15 | 16 | public void setIsEnchantable(boolean flag) { 17 | enchantable.setValue(flag); 18 | } 19 | 20 | public boolean getIsEnchantable() { 21 | return enchantable.getValue(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/structure/StructureBlockInfoModification.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.structure; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.nbt.CompoundTag; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.world.level.block.Block; 7 | import net.minecraft.world.level.block.state.BlockState; 8 | 9 | import javax.annotation.Nullable; 10 | import java.util.Map; 11 | 12 | public interface StructureBlockInfoModification { 13 | String getId(); 14 | 15 | Block getBlock(); 16 | 17 | void setBlock(ResourceLocation id); 18 | 19 | void setBlock(ResourceLocation id, Map properties); 20 | 21 | Map getProperties(); 22 | 23 | boolean hasNbt(); 24 | 25 | @Nullable 26 | CompoundTag getNbt(); 27 | 28 | void setNbt(@Nullable CompoundTag nbt); 29 | 30 | BlockPos getPosition(); 31 | 32 | void setVanillaBlockState(BlockState state); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/SimpleTrade.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.trades; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeItem; 4 | import net.minecraft.util.RandomSource; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.item.trading.MerchantOffer; 8 | 9 | import javax.annotation.Nullable; 10 | 11 | public class SimpleTrade extends TransformableTrade { 12 | protected TradeItem output; 13 | 14 | public SimpleTrade(TradeItem[] input, TradeItem output) { 15 | super(input); 16 | this.output = output; 17 | } 18 | 19 | public TradeItem getOutput() { 20 | return output; 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public MerchantOffer createOffer(Entity trader, RandomSource random) { 26 | ItemStack oi = this.output.createItemStack(random); 27 | return createOffer(oi, random); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/BasicItemListingMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeMatcher; 4 | import com.almostreliable.morejs.features.villager.TradeTypes; 5 | import net.minecraft.world.item.ItemStack; 6 | import net.neoforged.neoforge.common.BasicItemListing; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | 11 | @Mixin(BasicItemListing.class) 12 | public class BasicItemListingMixin implements TradeMatcher.Filterable { 13 | @Shadow(remap = false) @Final protected ItemStack price; 14 | 15 | @Shadow(remap = false) @Final protected ItemStack price2; 16 | 17 | @Shadow(remap = false) @Final protected ItemStack forSale; 18 | 19 | @Override 20 | public boolean matchesTradeFilter(TradeMatcher filter) { 21 | return filter.match(this.price, this.price2, this.forSale, TradeTypes.ForgeBasic); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/PotionBrewingBuilderAccessor.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin; 2 | 3 | import net.minecraft.world.item.Item; 4 | import net.minecraft.world.item.alchemy.Potion; 5 | import net.minecraft.world.item.alchemy.PotionBrewing; 6 | import net.minecraft.world.item.crafting.Ingredient; 7 | import net.neoforged.neoforge.common.brewing.IBrewingRecipe; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.gen.Accessor; 10 | 11 | import java.util.List; 12 | 13 | @Mixin(PotionBrewing.Builder.class) 14 | public interface PotionBrewingBuilderAccessor { 15 | 16 | @Accessor("containers") 17 | List morejs$getContainers(); 18 | 19 | @Accessor("potionMixes") 20 | List> morejs$getPotionMixes(); 21 | 22 | @Accessor("containerMixes") 23 | List> morejs$getContainerMixes(); 24 | 25 | @Accessor(value = "recipes", remap = false) 26 | List morejs$getRecipes(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/VillagerTradingManagerMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.features.villager.TradingManager; 4 | import net.neoforged.neoforge.common.VillagerTradingManager; 5 | import net.neoforged.neoforge.event.TagsUpdatedEvent; 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(value = VillagerTradingManager.class, priority = 1337) 12 | public class VillagerTradingManagerMixin { 13 | 14 | @Inject(method = "loadTrades", at = @At("RETURN"), remap = false) 15 | private static void postTradeLoading(TagsUpdatedEvent e, CallbackInfo ci) { 16 | if (e.getUpdateCause() == TagsUpdatedEvent.UpdateCause.SERVER_DATA_LOAD) { 17 | TradingManager.invokeVillagerTradeEvent(); 18 | TradingManager.invokeWanderingTradeEvent(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/IntRange.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import net.minecraft.util.RandomSource; 4 | 5 | import java.util.function.IntPredicate; 6 | 7 | public class IntRange implements IntPredicate { 8 | private final int min; 9 | private final int max; 10 | 11 | public IntRange(int min, int max) { 12 | this.min = min; 13 | this.max = max; 14 | } 15 | 16 | public IntRange(int level) { 17 | this(level, level); 18 | } 19 | 20 | public static IntRange all() { 21 | return new IntRange(0, Integer.MAX_VALUE); 22 | } 23 | 24 | @Override 25 | public boolean test(int value) { 26 | return min <= value && value <= max; 27 | } 28 | 29 | public int getMax() { 30 | return max; 31 | } 32 | 33 | public int getMin() { 34 | return min; 35 | } 36 | 37 | public int getRandom(RandomSource random) { 38 | if (min == max) { 39 | return min; 40 | } 41 | 42 | return random.nextIntBetweenInclusive(min, max); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | public net.minecraft.world.entity.npc.VillagerTrades$EmeraldForItems 2 | public net.minecraft.world.entity.npc.VillagerTrades$DyedArmorForEmeralds 3 | public net.minecraft.world.entity.npc.VillagerTrades$EmeraldsForVillagerTypeItem 4 | public net.minecraft.world.entity.npc.VillagerTrades$EnchantBookForEmeralds 5 | public net.minecraft.world.entity.npc.VillagerTrades$EnchantedItemForEmeralds 6 | public net.minecraft.world.entity.npc.VillagerTrades$ItemsAndEmeraldsToItems 7 | public net.minecraft.world.entity.npc.VillagerTrades$ItemsForEmeralds 8 | public net.minecraft.world.entity.npc.VillagerTrades$SuspiciousStewForEmerald 9 | public net.minecraft.world.entity.npc.VillagerTrades$TippedArrowForItemsAndEmeralds 10 | public net.minecraft.world.entity.npc.VillagerTrades$TreasureMapForEmeralds 11 | 12 | public net.minecraft.client.gui.screens.inventory.MerchantScreen$TradeOfferButton 13 | 14 | public net.minecraft.world.item.alchemy.PotionBrewing$Mix 15 | public net.minecraft.world.item.alchemy.PotionBrewing$Mix (Lnet/minecraft/core/Holder;Lnet/minecraft/world/item/crafting/Ingredient;Lnet/minecraft/core/Holder;)V # 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/CustomTrade.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.trades; 2 | 3 | import net.minecraft.util.RandomSource; 4 | import net.minecraft.world.entity.Entity; 5 | import net.minecraft.world.entity.npc.VillagerTrades; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.item.Items; 8 | import net.minecraft.world.item.trading.ItemCost; 9 | import net.minecraft.world.item.trading.MerchantOffer; 10 | 11 | import javax.annotation.Nullable; 12 | 13 | public class CustomTrade implements VillagerTrades.ItemListing { 14 | 15 | private final TransformableTrade.Transformer transformer; 16 | 17 | public CustomTrade(TransformableTrade.Transformer transformer) { 18 | this.transformer = transformer; 19 | } 20 | 21 | @Nullable 22 | @Override 23 | public MerchantOffer getOffer(Entity entity, RandomSource random) { 24 | MerchantOffer offer = new MerchantOffer(new ItemCost(Items.EMERALD), 25 | new ItemStack(Items.EMERALD), 26 | 16, 27 | 2, 28 | 0.05f); 29 | transformer.accept(offer, entity, random); 30 | return offer; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/structure/StructureTemplateMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.structure; 2 | 3 | import com.almostreliable.morejs.features.structure.StructureTemplateAccess; 4 | import net.minecraft.core.Vec3i; 5 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | 10 | import java.util.List; 11 | 12 | @Mixin(StructureTemplate.class) 13 | public class StructureTemplateMixin implements StructureTemplateAccess { 14 | 15 | @Shadow @Final private List palettes; 16 | 17 | @Shadow @Final private List entityInfoList; 18 | 19 | @Shadow private Vec3i size; 20 | 21 | @Override 22 | public List getPalettes() { 23 | return this.palettes; 24 | } 25 | 26 | @Override 27 | public List getEntities() { 28 | return this.entityInfoList; 29 | } 30 | 31 | @Override 32 | public Vec3i getBorderSize() { 33 | return this.size; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/potion/CustomBrewingFilter.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.potion; 2 | 3 | import com.almostreliable.morejs.util.Utils; 4 | import net.minecraft.world.item.crafting.Ingredient; 5 | import net.neoforged.neoforge.common.brewing.BrewingRecipe; 6 | 7 | import java.util.Optional; 8 | import java.util.function.Predicate; 9 | 10 | public record CustomBrewingFilter(Optional ingredient, Optional input, 11 | Optional output) implements Predicate { 12 | 13 | @Override 14 | public boolean test(BrewingRecipe brewingRecipe) { 15 | if (input().isPresent() && 16 | input.filter(input -> Utils.matchesIngredient(input, brewingRecipe.getInput())).isEmpty()) { 17 | return false; 18 | } 19 | 20 | if (output().isPresent() && output.filter(output -> output.test(brewingRecipe.getOutput())).isEmpty()) { 21 | return false; 22 | } 23 | 24 | return ingredient().isEmpty() || ingredient 25 | .filter(ingredient -> Utils.matchesIngredient(ingredient, brewingRecipe.getIngredient())) 26 | .isPresent(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/events/StartTradingEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.events; 2 | 3 | import com.almostreliable.morejs.features.villager.OfferExtension; 4 | import dev.latvian.mods.kubejs.player.KubePlayerEvent; 5 | import net.minecraft.world.entity.player.Player; 6 | import net.minecraft.world.item.trading.Merchant; 7 | import net.minecraft.world.item.trading.MerchantOffer; 8 | 9 | import java.util.function.BiConsumer; 10 | 11 | public class StartTradingEventJS implements KubePlayerEvent { 12 | private final Player player; 13 | private final Merchant merchant; 14 | 15 | public StartTradingEventJS(Player player, Merchant merchant) { 16 | this.player = player; 17 | this.merchant = merchant; 18 | } 19 | 20 | @Override 21 | public Player getEntity() { 22 | return player; 23 | } 24 | 25 | public Merchant getMerchant() { 26 | return merchant; 27 | } 28 | 29 | public void forEachOffers(BiConsumer consumer) { 30 | for (int i = 0; i < merchant.getOffers().size(); i++) { 31 | MerchantOffer offer = merchant.getOffers().get(i); 32 | consumer.accept((OfferExtension) offer, i); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/potion/PotionBrewingFilter.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.potion; 2 | 3 | import com.almostreliable.morejs.util.Utils; 4 | import net.minecraft.core.HolderSet; 5 | import net.minecraft.world.item.alchemy.Potion; 6 | import net.minecraft.world.item.alchemy.PotionBrewing; 7 | import net.minecraft.world.item.crafting.Ingredient; 8 | 9 | import java.util.Optional; 10 | import java.util.function.Predicate; 11 | 12 | public record PotionBrewingFilter(Optional ingredient, Optional> input, 13 | Optional> output) implements Predicate> { 14 | 15 | @Override 16 | public boolean test(PotionBrewing.Mix potionMix) { 17 | if (input.isPresent() && input.filter(input -> input.contains(potionMix.from())).isEmpty()) { 18 | return false; 19 | } 20 | 21 | if (output.isPresent() && output.filter(output -> output.contains(potionMix.to())).isEmpty()) { 22 | return false; 23 | } 24 | 25 | return ingredient().isEmpty() || ingredient 26 | .filter(ingredient -> Utils.matchesIngredient(ingredient, potionMix.ingredient())) 27 | .isPresent(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/EnchantmentTableEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import dev.latvian.mods.kubejs.level.KubeLevelEvent; 4 | import net.minecraft.world.entity.player.Player; 5 | import net.minecraft.world.inventory.EnchantmentMenu; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.level.Level; 8 | 9 | public class EnchantmentTableEventJS implements KubeLevelEvent { 10 | protected final EnchantmentMenu menu; 11 | 12 | protected ItemStack item; 13 | private final ItemStack secondItem; 14 | private final Player player; 15 | private final Level level; 16 | 17 | public EnchantmentTableEventJS(ItemStack item, ItemStack secondItem, Level level, Player player, EnchantmentMenu menu) { 18 | this.item = item; 19 | this.secondItem = secondItem; 20 | this.level = level; 21 | this.player = player; 22 | this.menu = menu; 23 | } 24 | 25 | @Override 26 | public Level getLevel() { 27 | return level; 28 | } 29 | 30 | public Player getPlayer() { 31 | return player; 32 | } 33 | 34 | public ItemStack getItem() { 35 | return item; 36 | } 37 | 38 | public ItemStack getSecondItem() { 39 | return secondItem; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/structure/StructureManagerMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.structure; 2 | 3 | import com.almostreliable.morejs.features.structure.StructureLoadEventJS; 4 | import net.minecraft.resources.ResourceLocation; 5 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 6 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | 12 | import java.util.Optional; 13 | 14 | @Mixin(StructureTemplateManager.class) 15 | public class StructureManagerMixin { 16 | 17 | @Inject(method = "loadFromResource", at = @At("RETURN")) 18 | private void morejs$invokeEventFromResource(ResourceLocation id, CallbackInfoReturnable> cir) { 19 | cir.getReturnValue().ifPresent(structureTemplate -> StructureLoadEventJS.invoke(structureTemplate, id)); 20 | } 21 | 22 | @Inject(method = "loadFromGenerated", at = @At("RETURN")) 23 | private void morejs$invokeEventFromGenerated(ResourceLocation id, CallbackInfoReturnable> cir) { 24 | cir.getReturnValue().ifPresent(structureTemplate -> StructureLoadEventJS.invoke(structureTemplate, id)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example_scripts/potion_brewing.js: -------------------------------------------------------------------------------- 1 | // removeVanillaSplashContainer 2 | MoreJS.registerPotionBrewing(event => { 3 | event.removeContainer("minecraft:splash_potion"); 4 | }); 5 | 6 | // addCustomContainer 7 | MoreJS.registerPotionBrewing(event => { 8 | event.addContainerRecipe("minecraft:apple", "minecraft:lingering_potion", "minecraft:diamond"); 9 | }); 10 | 11 | // addPotionBrewing 12 | MoreJS.registerPotionBrewing(event => { 13 | event.addPotionBrewing("minecraft:apple", "minecraft:water", "minecraft:strong_regeneration"); 14 | }); 15 | 16 | // removePotionBrewing 17 | MoreJS.registerPotionBrewing(event => { 18 | event.removePotionBrewing({ 19 | ingredient: "minecraft:apple", 20 | input: "minecraft:harming", 21 | output: "minecraft:strong_harming", 22 | }) 23 | }); 24 | 25 | // addCustomBrewing 26 | MoreJS.registerPotionBrewing(event => { 27 | event.addCustomBrewing("minecraft:stick", "minecraft:oak_log", "minecraft:acacia_log"); 28 | }); 29 | 30 | // removeCustomBrewing 31 | MoreJS.registerPotionBrewing(event => { 32 | event.addCustomBrewing("minecraft:emerald", "minecraft:nether_star", "minecraft:diamond"); 33 | event.removeCustomBrewing({ 34 | ingredient: "minecraft:emerald", 35 | input: "minecraft:nether_star", 36 | output: "minecraft:diamond", 37 | }) 38 | }); 39 | 40 | 41 | MoreJS.registerPotionBrewing(event => { 42 | event.removePotionBrewing({ 43 | input: "water" 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | target_version: 7 | type: string 8 | required: false 9 | description: "mod version | empty = next option" 10 | update_type: 11 | type: choice 12 | required: false 13 | description: "update type" 14 | default: "minor" 15 | options: 16 | - "major" 17 | - "minor" 18 | - "patch" 19 | - "none" 20 | release_type: 21 | type: choice 22 | required: true 23 | description: "type of release" 24 | default: "release" 25 | options: 26 | - "alpha" 27 | - "beta" 28 | - "release" 29 | debug: 30 | type: boolean 31 | required: false 32 | default: false 33 | description: "enable debug mode (GitHub only)" 34 | 35 | jobs: 36 | redirect: 37 | uses: AlmostReliable/.github/.github/workflows/release-nf.yml@main 38 | secrets: inherit 39 | with: 40 | java-distribution: "microsoft" 41 | java-version: "21" 42 | mod_name: "MoreJS" 43 | curseforge_id: "666198" 44 | modrinth_id: "mo64mR1W" 45 | dependencies: | 46 | kubejs(required){curseforge:238086}{modrinth:umyGl7zF} 47 | target_version: ${{ github.event.inputs.target_version }} 48 | update_type: ${{ github.event.inputs.update_type }} 49 | release_type: ${{ github.event.inputs.release_type }} 50 | loaders: "neoforge" 51 | debug: ${{ github.event.inputs.debug }} 52 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/Plugin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.villager.IntRange; 5 | import com.almostreliable.morejs.features.villager.TradeItem; 6 | import com.almostreliable.morejs.features.villager.VillagerUtils; 7 | import com.almostreliable.morejs.util.WeightedList; 8 | import dev.latvian.mods.kubejs.event.EventGroupRegistry; 9 | import dev.latvian.mods.kubejs.plugin.KubeJSPlugin; 10 | import dev.latvian.mods.kubejs.script.BindingRegistry; 11 | import dev.latvian.mods.kubejs.script.TypeWrapperRegistry; 12 | import net.minecraft.world.item.enchantment.EnchantmentInstance; 13 | 14 | public class Plugin implements KubeJSPlugin { 15 | 16 | @Override 17 | public void registerBindings(BindingRegistry event) { 18 | event.add("VillagerUtils", VillagerUtils.class); 19 | event.add("TradeItem", TradeItem.class); 20 | event.add("MoreUtils", MoreJSBinding.class); 21 | event.add("EnchantmentInstance", EnchantmentInstance.class); 22 | } 23 | 24 | @Override 25 | public void registerTypeWrappers(TypeWrapperRegistry typeWrappers) { 26 | typeWrappers.register(TradeItem.class, MoreJSBinding::ofTradeItem); 27 | typeWrappers.register(IntRange.class, MoreJSBinding::range); 28 | typeWrappers.register(WeightedList.class, MoreJSBinding::ofWeightedList); 29 | } 30 | 31 | @Override 32 | public void registerEvents(EventGroupRegistry registry) { 33 | registry.register(Events.GROUP); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/morejs.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "com.almostreliable.morejs.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "PotionBrewingBuilderAccessor", 8 | "enchanting.EnchantmentMenuMixin", 9 | "entity.PiglinSpecificSensorMixin", 10 | "structure.StructureBlockInfoMixin", 11 | "structure.StructureManagerMixin", 12 | "structure.StructureStartMixin", 13 | "structure.StructureTemplateMixin", 14 | "villager.AbstractVillagerMixin", 15 | "villager.BasicItemListingMixin", 16 | "villager.MerchantMenuMixin", 17 | "villager.MerchantOfferAccessor", 18 | "villager.MerchantOfferMixin", 19 | "villager.VillagerMixin", 20 | "villager.VillagerTradesMixin$EmeraldForItemsMixin", 21 | "villager.VillagerTradesMixin$EmeraldsForVillagerTypeItemMixin", 22 | "villager.VillagerTradesMixin$EnchantBookForEmeraldsMixin", 23 | "villager.VillagerTradesMixin$EnchantedItemForEmeraldsMixin", 24 | "villager.VillagerTradesMixin$ItemsAndEmeraldsToItemsMixin", 25 | "villager.VillagerTradesMixin$ItemsForEmeraldsMixin", 26 | "villager.VillagerTradesMixin$SuspiciousStewForEmeraldMixin", 27 | "villager.VillagerTradesMixin$TippedArrowForItemsAndEmeraldsMixin", 28 | "villager.VillagerTradesMixin$TreasureMapForEmeraldsMixin", 29 | "villager.VillagerTradingManagerMixin", 30 | "villager.WanderingTraderMixin" 31 | ], 32 | "client": [ 33 | "enchanting.EnchantmentScreenMixin", 34 | "villager.MerchantScreenMixin" 35 | ], 36 | "injectors": { 37 | "defaultRequire": 1 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/AbstractVillagerMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.villager.events.SingleUpdateOfferEventJS; 5 | import com.llamalad7.mixinextras.sugar.Local; 6 | import com.llamalad7.mixinextras.sugar.ref.LocalIntRef; 7 | import net.minecraft.world.entity.npc.AbstractVillager; 8 | import net.minecraft.world.entity.npc.VillagerTrades; 9 | import net.minecraft.world.item.trading.MerchantOffer; 10 | import net.minecraft.world.item.trading.MerchantOffers; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Redirect; 14 | 15 | @Mixin(value = AbstractVillager.class, priority = 42) 16 | public class AbstractVillagerMixin { 17 | 18 | @Redirect(method = "addOffersFromItemListings", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/trading/MerchantOffers;add(Ljava/lang/Object;)Z")) 19 | private boolean mid$foo(MerchantOffers offers, Object o, MerchantOffers givenMerchantOffers, VillagerTrades.ItemListing[] possibleTrades, int maxNumbers, @Local(ordinal = 1) LocalIntRef i) { 20 | MerchantOffer offer = (MerchantOffer) o; 21 | var e = new SingleUpdateOfferEventJS((AbstractVillager) (Object) this, offers, possibleTrades, offer); 22 | if (Events.UPDATE_OFFER.post(e).interruptFalse()) { 23 | i.set(i.get() - 1); 24 | return false; 25 | } 26 | 27 | return offers.add(e.getOffer()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/MerchantMenuMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.villager.events.StartTradingEventJS; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import net.minecraft.world.entity.npc.ClientSideMerchant; 7 | import net.minecraft.world.entity.player.Inventory; 8 | import net.minecraft.world.inventory.AbstractContainerMenu; 9 | import net.minecraft.world.inventory.MenuType; 10 | import net.minecraft.world.inventory.MerchantMenu; 11 | import net.minecraft.world.item.trading.Merchant; 12 | import org.jetbrains.annotations.Nullable; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(MerchantMenu.class) 19 | public abstract class MerchantMenuMixin extends AbstractContainerMenu { 20 | 21 | protected MerchantMenuMixin(@Nullable MenuType menuType, int i) { 22 | // IGNORE THIS. 23 | super(menuType, i); 24 | } 25 | 26 | @Inject(method = "(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/item/trading/Merchant;)V", at = @At("RETURN")) 27 | public void invokeOpenTradeEvent(int windowId, Inventory inventory, Merchant merchant, CallbackInfo ci) { 28 | if (merchant instanceof ClientSideMerchant && !(inventory.player instanceof ServerPlayer)) return; 29 | Events.PLAYER_START_TRADING.post(new StartTradingEventJS(inventory.player, merchant)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/teleport/EntityTeleportsEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.teleport; 2 | 3 | import dev.latvian.mods.kubejs.entity.KubeEntityEvent; 4 | import net.minecraft.world.entity.Entity; 5 | import net.minecraft.world.level.Level; 6 | 7 | import javax.annotation.Nullable; 8 | 9 | public class EntityTeleportsEventJS implements KubeEntityEvent { 10 | 11 | private final Entity entity; 12 | private final TeleportType type; 13 | private final Level level; 14 | private double x; 15 | private double y; 16 | private double z; 17 | 18 | public EntityTeleportsEventJS(Entity entity, double x, double y, double z, TeleportType type) { 19 | this(entity, x, y, z, null, type); 20 | } 21 | 22 | public EntityTeleportsEventJS(Entity entity, double x, double y, double z, @Nullable Level level, TeleportType type) { 23 | this.entity = entity; 24 | this.x = x; 25 | this.y = y; 26 | this.z = z; 27 | this.type = type; 28 | this.level = level; 29 | } 30 | 31 | public TeleportType getType() { 32 | return type; 33 | } 34 | 35 | @Override 36 | public Entity getEntity() { 37 | return entity; 38 | } 39 | 40 | public double getX() { 41 | return x; 42 | } 43 | 44 | public void setX(double x) { 45 | this.x = x; 46 | } 47 | 48 | public double getY() { 49 | return y; 50 | } 51 | 52 | public void setY(double y) { 53 | this.y = y; 54 | } 55 | 56 | public double getZ() { 57 | return z; 58 | } 59 | 60 | public void setZ(double z) { 61 | this.z = z; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/PlayerEnchantEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.resources.ResourceKey; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.item.enchantment.EnchantmentInstance; 9 | import net.minecraft.world.level.Level; 10 | 11 | import java.util.List; 12 | 13 | public class PlayerEnchantEventJS extends EnchantmentTableEventJS { 14 | private final BlockPos pos; 15 | private final int requiredLevel; 16 | private final List enchantments; 17 | 18 | public PlayerEnchantEventJS(ItemStack item, ItemStack secondItem, Level level, BlockPos pos, Player player, EnchantmentMenuState state, int requiredLevel, List enchantments) { 19 | super(item, secondItem, level, player, state.getMenu()); 20 | this.pos = pos; 21 | this.requiredLevel = requiredLevel; 22 | this.enchantments = enchantments; 23 | } 24 | 25 | public List getEnchantments() { 26 | return enchantments; 27 | } 28 | 29 | public List getEnchantmentIds() { 30 | return getEnchantments() 31 | .stream() 32 | .flatMap(e -> e.enchantment.unwrapKey().stream()) 33 | .map(ResourceKey::location) 34 | .toList(); 35 | } 36 | 37 | public int getRequiredLevel() { 38 | return requiredLevel; 39 | } 40 | 41 | public BlockPos getPosition() { 42 | return pos; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/events/PostUpdateOfferEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.events; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import net.minecraft.world.entity.npc.AbstractVillager; 5 | import net.minecraft.world.entity.npc.Villager; 6 | import net.minecraft.world.entity.npc.VillagerProfession; 7 | import net.minecraft.world.entity.npc.VillagerTrades; 8 | import net.minecraft.world.item.trading.MerchantOffer; 9 | import net.minecraft.world.item.trading.MerchantOffers; 10 | 11 | import javax.annotation.Nullable; 12 | 13 | public class PostUpdateOfferEventJS extends UpdateOfferEventJS { 14 | 15 | @SuppressWarnings("ConstantValue") 16 | public static void invoke(AbstractVillager villager, MerchantOffers allOffers) { 17 | if (villager instanceof Villager v) { 18 | var data = v.getVillagerData(); 19 | if (data == null) { 20 | return; 21 | } 22 | 23 | if (data.getProfession() == VillagerProfession.NONE) { 24 | return; 25 | } 26 | } 27 | 28 | Events.POST_UPDATE_OFFERS.post(new PostUpdateOfferEventJS(villager, allOffers)); 29 | } 30 | 31 | public PostUpdateOfferEventJS(AbstractVillager villager, MerchantOffers allOffers) { 32 | super(villager, allOffers); 33 | } 34 | 35 | public void addOffer(@Nullable MerchantOffer offer) { 36 | if (offer != null) { 37 | getAllOffers().add(offer); 38 | } 39 | } 40 | 41 | public void addTrade(VillagerTrades.ItemListing trade) { 42 | var offer = trade.getOffer(getEntity(), getRandom()); 43 | addOffer(offer); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/StewTrade.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.trades; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeItem; 4 | import com.google.common.base.Preconditions; 5 | import net.minecraft.core.component.DataComponents; 6 | import net.minecraft.core.registries.BuiltInRegistries; 7 | import net.minecraft.util.RandomSource; 8 | import net.minecraft.world.effect.MobEffect; 9 | import net.minecraft.world.entity.Entity; 10 | import net.minecraft.world.item.ItemStack; 11 | import net.minecraft.world.item.Items; 12 | import net.minecraft.world.item.component.SuspiciousStewEffects; 13 | import net.minecraft.world.item.trading.MerchantOffer; 14 | 15 | import javax.annotation.Nullable; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | public class StewTrade extends TransformableTrade { 20 | 21 | 22 | private final List effects = new ArrayList<>(); 23 | 24 | public StewTrade(TradeItem[] inputs) { 25 | super(inputs); 26 | } 27 | 28 | public StewTrade addEffect(MobEffect effect, int duration) { 29 | Preconditions.checkArgument(duration > 0, "Duration must be greater than 0"); 30 | var mobEffectHolder = BuiltInRegistries.MOB_EFFECT.wrapAsHolder(effect); 31 | effects.add(new SuspiciousStewEffects.Entry(mobEffectHolder, duration)); 32 | return this; 33 | } 34 | 35 | @Nullable 36 | @Override 37 | public MerchantOffer createOffer(Entity entity, RandomSource random) { 38 | ItemStack stew = new ItemStack(Items.SUSPICIOUS_STEW); 39 | 40 | var effectCopy = List.copyOf(effects); 41 | stew.set(DataComponents.SUSPICIOUS_STEW_EFFECTS, new SuspiciousStewEffects(effectCopy)); 42 | return createOffer(stew, random); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/events/SingleUpdateOfferEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.events; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.minecraft.world.entity.npc.AbstractVillager; 5 | import net.minecraft.world.entity.npc.VillagerTrades; 6 | import net.minecraft.world.item.trading.MerchantOffer; 7 | import net.minecraft.world.item.trading.MerchantOffers; 8 | 9 | import javax.annotation.Nullable; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | public class SingleUpdateOfferEventJS extends UpdateOfferEventJS { 14 | 15 | private final VillagerTrades.ItemListing[] possibleTrades; 16 | private MerchantOffer offer; 17 | @Nullable private List cachedWandererTrades; 18 | 19 | public SingleUpdateOfferEventJS(AbstractVillager villager, MerchantOffers allOffers, VillagerTrades.ItemListing[] possibleTrades, MerchantOffer offer) { 20 | super(villager, allOffers); 21 | this.possibleTrades = possibleTrades; 22 | this.offer = offer; 23 | } 24 | 25 | public List getUsedTrades() { 26 | return Arrays.asList(possibleTrades); 27 | } 28 | 29 | public MerchantOffer getOffer() { 30 | return offer; 31 | } 32 | 33 | public void setOffer(MerchantOffer offer) { 34 | Preconditions.checkNotNull(offer, "Offer must not be null"); 35 | this.offer = offer; 36 | } 37 | 38 | public void setOffer(VillagerTrades.ItemListing trade) { 39 | MerchantOffer newOffer = trade.getOffer(getEntity(), getLevel().getRandom()); 40 | if (newOffer != null) { 41 | this.offer = newOffer; 42 | } 43 | } 44 | 45 | @Nullable 46 | public MerchantOffer createRandomOffer() { 47 | return createRandomOffer(getUsedTrades()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/util/ResourceOrTag.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.util; 2 | 3 | import com.mojang.datafixers.util.Either; 4 | import net.minecraft.core.Holder; 5 | import net.minecraft.core.HolderSet; 6 | import net.minecraft.core.Registry; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.resources.ResourceKey; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.tags.TagKey; 11 | 12 | import java.util.Optional; 13 | import java.util.function.Predicate; 14 | 15 | // TODO remove, we can now use HolderSet I guess 16 | public class ResourceOrTag { 17 | private final Either, TagKey> either; 18 | 19 | private ResourceOrTag(Either, TagKey> either) { 20 | this.either = either; 21 | } 22 | 23 | public static ResourceOrTag get(String s, ResourceKey> registry) { 24 | if (s.startsWith("#")) { 25 | ResourceLocation rl = ResourceLocation.parse(s.substring(1)); 26 | return new ResourceOrTag<>(Either.right(TagKey.create(registry, rl))); 27 | } 28 | 29 | ResourceLocation rl = ResourceLocation.parse(s); 30 | return new ResourceOrTag<>(Either.left(ResourceKey.create(registry, rl))); 31 | } 32 | 33 | public Optional> asHolderSet(Registry registry) { 34 | return either.map(id -> registry.getHolder(id).map(HolderSet::direct), registry::getTag); 35 | } 36 | 37 | public Predicate> asHolderPredicate() { 38 | return either.map(id -> holder -> holder.is(id), tag -> holder -> holder.is(tag)); 39 | } 40 | 41 | public Component getName() { 42 | String name = either.map(id -> id.location().toString(), tag -> "#" + tag.location().toString()); 43 | return Component.literal("Map for: " + Utils.format(name)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/testmod/mixin/ScriptManagerMixin.java: -------------------------------------------------------------------------------- 1 | package testmod.mixin; 2 | 3 | 4 | import com.almostreliable.morejs.BuildConfig; 5 | import dev.latvian.mods.kubejs.script.*; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | import java.nio.file.Path; 14 | import java.util.Map; 15 | 16 | @Mixin(value = ScriptManager.class, remap = false) 17 | public abstract class ScriptManagerMixin { 18 | 19 | @Shadow @Final public ScriptType scriptType; 20 | 21 | @Shadow @Final public Map packs; 22 | 23 | @Shadow 24 | protected abstract void loadFile(ScriptPack pack, ScriptFileInfo fileInfo); 25 | 26 | @Shadow 27 | public abstract void collectScripts(ScriptPack pack, Path dir, String path); 28 | 29 | @Inject(method = "reload", at = @At(value = "INVOKE", target = "Ldev/latvian/mods/kubejs/script/ScriptManager;load()V")) 30 | private void testmod$test(CallbackInfo ci) { 31 | if (scriptType != ScriptType.SERVER) { 32 | return; 33 | } 34 | 35 | String prop = System.getProperty(BuildConfig.MOD_ID + ".example_scripts"); 36 | if (prop == null) { 37 | return; 38 | } 39 | 40 | Path p = Path.of(prop); 41 | var packInfo = new ScriptPackInfo("server_examples", ""); 42 | var pack = new ScriptPack((ScriptManager) (Object) this, packInfo); 43 | this.collectScripts(pack, p, ""); 44 | 45 | for (var script : pack.info.scripts) { 46 | loadFile(pack, script); 47 | } 48 | 49 | pack.scripts.sort(null); 50 | this.packs.put("server_examples", pack); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/MerchantOfferCodecPatch.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import com.almostreliable.morejs.mixin.villager.MerchantOfferAccessor; 4 | import com.mojang.datafixers.util.Pair; 5 | import com.mojang.serialization.Codec; 6 | import com.mojang.serialization.DataResult; 7 | import com.mojang.serialization.DynamicOps; 8 | import net.minecraft.world.item.trading.MerchantOffer; 9 | 10 | public class MerchantOfferCodecPatch implements Codec { 11 | 12 | public static final String KEY = "morejs$isDisabled"; 13 | private final Codec codec; 14 | 15 | public static void patch() { 16 | var patch = new MerchantOfferCodecPatch(MerchantOffer.CODEC); 17 | MerchantOfferAccessor.morejs$setCodec(patch); 18 | } 19 | 20 | public MerchantOfferCodecPatch(Codec codec) { 21 | this.codec = codec; 22 | } 23 | 24 | @Override 25 | public DataResult> decode(DynamicOps ops, T input) { 26 | return codec.decode(ops, input).map(pair -> { 27 | MerchantOffer offer = pair.getFirst(); 28 | 29 | DataResult isDisabledResult = ops.get(input, KEY); 30 | isDisabledResult.flatMap(ops::getBooleanValue).ifSuccess(disabled -> { 31 | ((OfferExtension) offer).morejs$setDisabled(disabled); 32 | }); 33 | 34 | return pair; 35 | }); 36 | } 37 | 38 | @Override 39 | public DataResult encode(MerchantOffer offer, DynamicOps ops, T prefix) { 40 | return codec.encode(offer, ops, prefix).map(data -> { 41 | boolean isDisabled = ((OfferExtension) offer).morejs$isDisabled(); 42 | T isDisabledData = ops.createBoolean(isDisabled); 43 | return ops.set(data, KEY, isDisabledData); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/misc/ExperiencePlayerEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.misc; 2 | 3 | import dev.latvian.mods.kubejs.player.KubePlayerEvent; 4 | import net.minecraft.world.entity.player.Player; 5 | 6 | 7 | public class ExperiencePlayerEventJS implements KubePlayerEvent { 8 | 9 | private final Player player; 10 | private int amount; 11 | 12 | public ExperiencePlayerEventJS(Player player, int amount) { 13 | this.player = player; 14 | this.amount = amount; 15 | } 16 | 17 | public int getAmount() { 18 | return amount; 19 | } 20 | 21 | public void setAmount(int amount) { 22 | this.amount = amount; 23 | } 24 | 25 | @Override 26 | public Player getEntity() { 27 | return player; 28 | } 29 | 30 | public float getExperienceProgress() { 31 | return player.experienceProgress; 32 | } 33 | 34 | public void setExperienceProgress(float progress) { 35 | player.experienceProgress = progress; 36 | } 37 | 38 | public int getExperienceLevel() { 39 | return player.experienceLevel; 40 | } 41 | 42 | public void setExperienceLevel(int level) { 43 | player.experienceLevel = level; 44 | } 45 | 46 | public int getTotalExperience() { 47 | return player.totalExperience; 48 | } 49 | 50 | public void setTotalExperience(int experience) { 51 | player.totalExperience = experience; 52 | } 53 | 54 | public int getXpNeededForNextLevel() { 55 | return player.getXpNeededForNextLevel(); 56 | } 57 | 58 | public int getRemainingExperience() { 59 | return (int) (getXpNeededForNextLevel() - (getExperienceProgress() * getXpNeededForNextLevel())); 60 | } 61 | 62 | public boolean willLevelUp() { 63 | return getExperienceProgress() + (getAmount() / (float) getXpNeededForNextLevel()) >= 1.0F; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/WanderingTraderMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.villager.events.PostUpdateOfferEventJS; 5 | import com.almostreliable.morejs.features.villager.events.SingleUpdateOfferEventJS; 6 | import net.minecraft.world.entity.npc.AbstractVillager; 7 | import net.minecraft.world.entity.npc.VillagerTrades; 8 | import net.minecraft.world.entity.npc.WanderingTrader; 9 | import net.minecraft.world.item.trading.MerchantOffer; 10 | import net.minecraft.world.item.trading.MerchantOffers; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.Redirect; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | @Mixin(WanderingTrader.class) 18 | public abstract class WanderingTraderMixin { 19 | 20 | @Redirect(method = "updateTrades", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/trading/MerchantOffers;add(Ljava/lang/Object;)Z")) 21 | private boolean mid$foo(MerchantOffers offers, Object o) { 22 | MerchantOffer offer = (MerchantOffer) o; 23 | var e = new SingleUpdateOfferEventJS((AbstractVillager) (Object) this, 24 | offers, 25 | VillagerTrades.WANDERING_TRADER_TRADES.get(2), 26 | offer); 27 | if (Events.UPDATE_OFFER.post(e).interruptFalse()) { 28 | return false; 29 | } 30 | 31 | return offers.add(e.getOffer()); 32 | } 33 | 34 | @Inject(method = "updateTrades", at = @At(value = "RETURN")) 35 | private void morejs$invokePostUpdateOffer(CallbackInfo ci) { 36 | var self = (AbstractVillager) (Object) this; 37 | PostUpdateOfferEventJS.invoke(self, self.getOffers()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.util; 2 | 3 | import net.minecraft.world.item.crafting.Ingredient; 4 | import net.minecraft.world.item.trading.ItemCost; 5 | 6 | import javax.annotation.Nullable; 7 | import java.util.*; 8 | import java.util.stream.Collectors; 9 | 10 | public class Utils { 11 | public static Optional cast(Object o, Class type) { 12 | if (type.isInstance(o)) { 13 | return Optional.of(type.cast(o)); 14 | } 15 | 16 | return Optional.empty(); 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | public static T cast(Object o) { 21 | return (T) o; 22 | } 23 | 24 | @Nullable 25 | @SuppressWarnings("unchecked") 26 | public static T nullableCast(@Nullable Object o) { 27 | if (o == null) { 28 | return null; 29 | } 30 | return (T) o; 31 | } 32 | 33 | public static String format(String string) { 34 | int index = string.indexOf(":"); 35 | String sanitized = string.substring(index + 1).replaceAll("[#:._]", " ").trim(); 36 | return Arrays 37 | .stream(sanitized.split(" ")) 38 | .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1)) 39 | .collect(Collectors.joining(" ")); 40 | } 41 | 42 | public static List asList(Object o) { 43 | if (o instanceof List) { 44 | return Utils.cast(o); 45 | } 46 | 47 | if (o instanceof Object[]) { 48 | return new ArrayList<>(Arrays.asList((Object[]) o)); 49 | } 50 | 51 | return new ArrayList<>(Collections.singletonList(o)); 52 | } 53 | 54 | public static boolean matchesIngredient(Ingredient filter, Ingredient ingredient) { 55 | return Arrays.stream(filter.getItems()).anyMatch(ingredient); 56 | } 57 | 58 | public static boolean matchesItemCost(Ingredient filter, ItemCost itemCost) { 59 | return Arrays.stream(filter.getItems()).anyMatch(itemCost::test); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/misc/PiglinPlayerBehaviorEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.misc; 2 | 3 | import dev.latvian.mods.kubejs.player.KubePlayerEvent; 4 | import net.minecraft.world.entity.monster.piglin.Piglin; 5 | import net.minecraft.world.entity.player.Player; 6 | 7 | import javax.annotation.Nullable; 8 | import java.util.Optional; 9 | 10 | public class PiglinPlayerBehaviorEventJS implements KubePlayerEvent { 11 | 12 | private final Player player; 13 | private final Piglin piglin; 14 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 15 | private final Optional playerNotWearingGoldArmor; 16 | private PiglinBehavior behavior = PiglinBehavior.KEEP; 17 | private boolean ignoreHoldingCheck; 18 | 19 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 20 | public PiglinPlayerBehaviorEventJS(Piglin piglin, Player player, Optional playerNotWearingGoldArmor) { 21 | this.piglin = piglin; 22 | this.player = player; 23 | this.playerNotWearingGoldArmor = playerNotWearingGoldArmor; 24 | } 25 | 26 | public void ignoreHoldingCheck() { 27 | this.ignoreHoldingCheck = true; 28 | } 29 | 30 | @Override 31 | public Player getEntity() { 32 | return player; 33 | } 34 | 35 | public Piglin getPiglin() { 36 | return piglin; 37 | } 38 | 39 | public boolean isAggressiveAlready() { 40 | return playerNotWearingGoldArmor.isPresent(); 41 | } 42 | 43 | @Nullable 44 | public Player getPreviousTargetPlayer() { 45 | return playerNotWearingGoldArmor.orElse(null); 46 | } 47 | 48 | public PiglinBehavior getBehavior() { 49 | return behavior; 50 | } 51 | 52 | public void setBehavior(PiglinBehavior behavior) { 53 | this.behavior = behavior; 54 | } 55 | 56 | public boolean isIgnoreHoldingCheck() { 57 | return ignoreHoldingCheck; 58 | } 59 | 60 | public enum PiglinBehavior { 61 | ATTACK, 62 | IGNORE, 63 | KEEP 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/structure/StructureLoadEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.structure; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import dev.latvian.mods.kubejs.event.KubeEvent; 5 | import net.minecraft.core.Vec3i; 6 | import net.minecraft.resources.ResourceLocation; 7 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 8 | 9 | import java.util.function.Consumer; 10 | 11 | public class StructureLoadEventJS implements KubeEvent { 12 | private final StructureTemplateAccess structure; 13 | private final ResourceLocation id; 14 | 15 | public StructureLoadEventJS(StructureTemplateAccess structure, ResourceLocation id) { 16 | this.structure = structure; 17 | this.id = id; 18 | 19 | } 20 | 21 | public static void invoke(StructureTemplate structure, ResourceLocation id) { 22 | if (structure instanceof StructureTemplateAccess sta) { 23 | Events.STRUCTURE_LOAD.post(new StructureLoadEventJS(sta, id)); 24 | } 25 | } 26 | 27 | public Vec3i getStructureSize() { 28 | return structure.getBorderSize(); 29 | } 30 | 31 | public String getId() { 32 | return id.toString(); 33 | } 34 | 35 | public int getPalettesSize() { 36 | return structure.getPalettes().size(); 37 | } 38 | 39 | public int getEntitiesSize() { 40 | return structure.getEntities().size(); 41 | } 42 | 43 | public void removePalette(int index) { 44 | structure.getPalettes().remove(index); 45 | } 46 | 47 | public PaletteWrapper getPalette(int index) { 48 | return new PaletteWrapper(structure.getPalettes().get(index), structure.getBorderSize()); 49 | } 50 | 51 | public void forEachPalettes(Consumer consumer) { 52 | structure 53 | .getPalettes() 54 | .forEach(palette -> consumer.accept(new PaletteWrapper(palette, structure.getBorderSize()))); 55 | } 56 | 57 | public EntityInfoWrapper getEntities() { 58 | return new EntityInfoWrapper(structure.getEntities(), structure.getBorderSize()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/util/WeightedList.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.util; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.minecraft.util.RandomSource; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.function.Function; 9 | 10 | public class WeightedList { 11 | private static final RandomSource RANDOM = RandomSource.create(); 12 | private final List> entries; 13 | private final int totalWeight; 14 | 15 | private WeightedList(List> entries) { 16 | Preconditions.checkNotNull(entries, "entries are null"); 17 | Preconditions.checkArgument(!entries.isEmpty(), "entries cannot be empty"); 18 | this.entries = entries; 19 | this.totalWeight = entries.stream().mapToInt(Entry::weight).sum(); 20 | } 21 | 22 | public T roll() { 23 | return roll(RANDOM); 24 | } 25 | 26 | public T roll(RandomSource random) { 27 | int i = random.nextInt(totalWeight); 28 | for (Entry e : entries) { 29 | i -= e.weight; 30 | if (i < 0) { 31 | return e.value; 32 | } 33 | } 34 | throw new IllegalStateException("Rolled past end of list"); 35 | } 36 | 37 | public WeightedList map(Function mapper) { 38 | List> newEntries = new ArrayList<>(entries.size()); 39 | for (Entry entry : entries) { 40 | T2 newValue = mapper.apply(entry.value); 41 | if (newValue == null) { 42 | continue; 43 | } 44 | newEntries.add(new Entry<>(entry.weight, newValue)); 45 | } 46 | return new WeightedList<>(newEntries); 47 | } 48 | 49 | public static class Builder { 50 | private final List> entries = new ArrayList<>(); 51 | 52 | public Builder add(int weight, T value) { 53 | entries.add(new Entry<>(weight, value)); 54 | return this; 55 | } 56 | 57 | public WeightedList build() { 58 | return new WeightedList<>(entries); 59 | } 60 | } 61 | 62 | private record Entry(int weight, T value) {} 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/structure/StructureStartMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.structure; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.structure.StructureAfterPlaceEventJS; 5 | import net.minecraft.util.RandomSource; 6 | import net.minecraft.world.level.ChunkPos; 7 | import net.minecraft.world.level.StructureManager; 8 | import net.minecraft.world.level.WorldGenLevel; 9 | import net.minecraft.world.level.chunk.ChunkGenerator; 10 | import net.minecraft.world.level.levelgen.structure.BoundingBox; 11 | import net.minecraft.world.level.levelgen.structure.Structure; 12 | import net.minecraft.world.level.levelgen.structure.StructureStart; 13 | import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer; 14 | import org.spongepowered.asm.mixin.Final; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Shadow; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Inject; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | @Mixin(StructureStart.class) 22 | public class StructureStartMixin { 23 | 24 | @Shadow @Final private PiecesContainer pieceContainer; 25 | 26 | @Shadow @Final private Structure structure; 27 | 28 | @Inject(method = "placeInChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/Structure;afterPlace(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/levelgen/structure/pieces/PiecesContainer;)V", shift = At.Shift.AFTER)) 29 | private void morejs$invokeEventAfterPlace(WorldGenLevel worldGenLevel, StructureManager structureManager, ChunkGenerator chunkGenerator, RandomSource randomSource, BoundingBox boundingBox, ChunkPos chunkPos, CallbackInfo ci) { 30 | if (!Events.STRUCTURE_AFTER_PLACE.hasListeners()) return; 31 | var event = new StructureAfterPlaceEventJS(this.structure, 32 | worldGenLevel, 33 | structureManager, 34 | chunkGenerator, 35 | randomSource, 36 | boundingBox, 37 | chunkPos, 38 | this.pieceContainer); 39 | Events.STRUCTURE_AFTER_PLACE.post(event); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/TradeItem.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import dev.latvian.mods.kubejs.bindings.ItemWrapper; 4 | import net.minecraft.core.component.DataComponentPredicate; 5 | import net.minecraft.util.RandomSource; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.item.trading.ItemCost; 8 | 9 | import javax.annotation.Nullable; 10 | 11 | public class TradeItem { 12 | public static final TradeItem EMPTY = new TradeItem(ItemStack.EMPTY, null); 13 | 14 | private final ItemStack itemStack; 15 | @Nullable private final IntRange countRange; 16 | 17 | public static TradeItem of(ItemStack item) { 18 | return new TradeItem(ItemWrapper.of(item), null); 19 | } 20 | 21 | public static TradeItem of(ItemStack item, int price) { 22 | return of(item, price, price); 23 | } 24 | 25 | public static TradeItem of(ItemStack item, int min, int max) { 26 | return new TradeItem(ItemWrapper.of(item), new IntRange(min, max)); 27 | } 28 | 29 | public TradeItem(ItemStack itemStack, @Nullable IntRange countRange) { 30 | this.itemStack = itemStack; 31 | this.countRange = countRange; 32 | } 33 | 34 | public ItemStack createItemStack(RandomSource random) { 35 | if (itemStack.isEmpty()) { 36 | return ItemStack.EMPTY; 37 | } 38 | 39 | if (countRange == null) { 40 | return itemStack.copy(); 41 | } 42 | 43 | int c = countRange.getRandom(random); 44 | ItemStack stack = itemStack.copy(); 45 | stack.setCount(c); 46 | return stack; 47 | } 48 | 49 | public ItemCost createItemCost(RandomSource random) { 50 | ItemStack copy = itemStack.copy(); 51 | if (countRange != null) { 52 | copy.setCount(countRange.getRandom(random)); 53 | } 54 | 55 | DataComponentPredicate dcp = DataComponentPredicate.allOf(copy.getComponents()); 56 | //noinspection deprecation 57 | return new ItemCost(copy.getItem().builtInRegistryHolder(), copy.getCount(), dcp, copy); 58 | } 59 | 60 | public boolean isEmpty() { 61 | return itemStack.isEmpty(); 62 | } 63 | 64 | public ItemStack getItemStack() { 65 | return itemStack; 66 | } 67 | 68 | public IntRange getCountRange() { 69 | if (countRange == null) { 70 | return new IntRange(1, 1); 71 | } 72 | 73 | return countRange; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/enchanting/EnchantmentScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.enchanting; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.enchantment.EnchantmentMenuExtension; 5 | import com.almostreliable.morejs.features.enchantment.EnchantmentTableTooltipEventJS; 6 | import com.llamalad7.mixinextras.sugar.Local; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.client.gui.GuiGraphics; 9 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 10 | import net.minecraft.client.gui.screens.inventory.EnchantmentScreen; 11 | import net.minecraft.network.chat.Component; 12 | import net.minecraft.world.entity.player.Inventory; 13 | import net.minecraft.world.inventory.EnchantmentMenu; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | 19 | import java.util.List; 20 | 21 | @Mixin(EnchantmentScreen.class) 22 | public abstract class EnchantmentScreenMixin extends AbstractContainerScreen { 23 | 24 | public EnchantmentScreenMixin(EnchantmentMenu abstractContainerMenu, Inventory inventory, Component component) { 25 | // Ignore this 26 | super(abstractContainerMenu, inventory, component); 27 | } 28 | 29 | @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;renderComponentTooltip(Lnet/minecraft/client/gui/Font;Ljava/util/List;II)V")) 30 | private void render$InvokeEnchantmentTooltipMenu(GuiGraphics graphics, int mx, int my, float pTick, CallbackInfo ci, @Local(ordinal = 1) int slot, @Local List currentComponents) { 31 | if (Minecraft.getInstance().level == null || Minecraft.getInstance().player == null) { 32 | return; 33 | } 34 | 35 | if (this.menu instanceof EnchantmentMenuExtension extension) { 36 | EnchantmentTableTooltipEventJS e = new EnchantmentTableTooltipEventJS(extension 37 | .morejs$getContainer() 38 | .getItem(0), 39 | extension.morejs$getContainer().getItem(1), 40 | Minecraft.getInstance().level, 41 | Minecraft.getInstance().player, 42 | this.menu, 43 | slot, 44 | currentComponents); 45 | Events.ENCHANTMENT_TABLE_TOOLTIP.post(e); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/ForgeEventLoaders.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.misc.ExperiencePlayerEventJS; 5 | import com.almostreliable.morejs.features.potion.PotionBrewingRegisterEvent; 6 | import com.almostreliable.morejs.features.teleport.EntityTeleportsEventJS; 7 | import com.almostreliable.morejs.features.teleport.TeleportType; 8 | import net.neoforged.bus.api.EventPriority; 9 | import net.neoforged.bus.api.IEventBus; 10 | import net.neoforged.neoforge.common.NeoForge; 11 | import net.neoforged.neoforge.event.brewing.RegisterBrewingRecipesEvent; 12 | import net.neoforged.neoforge.event.entity.EntityTeleportEvent; 13 | import net.neoforged.neoforge.event.entity.player.PlayerXpEvent; 14 | 15 | public class ForgeEventLoaders { 16 | 17 | public static void load(IEventBus bus) { 18 | NeoForge.EVENT_BUS.addListener(ForgeEventLoaders::onExperienceChange); 19 | NeoForge.EVENT_BUS.addListener(ForgeEventLoaders::chorusFruitTeleport); 20 | NeoForge.EVENT_BUS.addListener(ForgeEventLoaders::enderPearlTeleport); 21 | NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, ForgeEventLoaders::onRegisterPotions); 22 | } 23 | 24 | private static void onRegisterPotions(RegisterBrewingRecipesEvent event) { 25 | Events.POTION_BREWING_REGISTER.post(new PotionBrewingRegisterEvent(event.getBuilder())); 26 | } 27 | 28 | private static void onExperienceChange(PlayerXpEvent.XpChange event) { 29 | var e = new ExperiencePlayerEventJS(event.getEntity(), event.getAmount()); 30 | var result = Events.XP_CHANGE.post(e); 31 | event.setAmount(e.getAmount()); 32 | if (result.interruptFalse()) { 33 | event.setCanceled(true); 34 | } 35 | } 36 | 37 | private static void chorusFruitTeleport(EntityTeleportEvent.ChorusFruit e) { 38 | handleEvent(e, TeleportType.CHORUS_FRUIT); 39 | } 40 | 41 | private static void enderPearlTeleport(EntityTeleportEvent.EnderPearl e) { 42 | handleEvent(e, TeleportType.ENDER_PEARL); 43 | } 44 | 45 | private static void handleEvent(EntityTeleportEvent e, TeleportType type) { 46 | var event = new EntityTeleportsEventJS(e.getEntity(), e.getTargetX(), e.getTargetY(), e.getTargetZ(), type); 47 | if (Events.TELEPORT.post(event).interruptFalse()) { 48 | e.setCanceled(true); 49 | return; 50 | } 51 | 52 | e.setTargetX(event.getX()); 53 | e.setTargetY(event.getY()); 54 | e.setTargetZ(event.getZ()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/EnchantedItemTrade.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.trades; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeItem; 4 | import com.mojang.datafixers.util.Either; 5 | import net.minecraft.core.HolderSet; 6 | import net.minecraft.core.registries.Registries; 7 | import net.minecraft.tags.TagKey; 8 | import net.minecraft.util.RandomSource; 9 | import net.minecraft.util.valueproviders.IntProvider; 10 | import net.minecraft.util.valueproviders.UniformInt; 11 | import net.minecraft.world.entity.Entity; 12 | import net.minecraft.world.item.ItemStack; 13 | import net.minecraft.world.item.enchantment.Enchantment; 14 | import net.minecraft.world.item.enchantment.EnchantmentHelper; 15 | import net.minecraft.world.item.trading.MerchantOffer; 16 | 17 | import javax.annotation.Nullable; 18 | import java.util.Optional; 19 | 20 | public class EnchantedItemTrade extends TransformableTrade { 21 | 22 | private final ItemStack itemToEnchant; 23 | private IntProvider enchantLevels = UniformInt.of(5, 20); 24 | private final Either, HolderSet> tradeableEnchantments; 25 | 26 | public EnchantedItemTrade(TradeItem[] inputs, ItemStack itemToEnchant, TagKey tradeableEnchantments) { 27 | super(inputs); 28 | this.itemToEnchant = itemToEnchant; 29 | this.tradeableEnchantments = Either.left(tradeableEnchantments); 30 | } 31 | 32 | public EnchantedItemTrade(TradeItem[] inputs, ItemStack itemToEnchant, HolderSet enchantments) { 33 | super(inputs); 34 | this.itemToEnchant = itemToEnchant; 35 | this.tradeableEnchantments = Either.right(enchantments); 36 | } 37 | 38 | public EnchantedItemTrade levels(IntProvider levels) { 39 | this.enchantLevels = levels; 40 | return this; 41 | } 42 | 43 | @Nullable 44 | @Override 45 | public MerchantOffer createOffer(Entity entity, RandomSource random) { 46 | int levels = enchantLevels.sample(random); 47 | var registryAccess = entity.level().registryAccess(); 48 | var possibleEnchantments = tradeableEnchantments.map(tag -> registryAccess 49 | .registryOrThrow(Registries.ENCHANTMENT) 50 | .getTag(tag), Optional::of); 51 | 52 | ItemStack result = EnchantmentHelper.enchantItem(random, 53 | itemToEnchant.copy(), 54 | levels, 55 | registryAccess, 56 | possibleEnchantments); 57 | return createOffer(result, random); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/OfferExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import com.almostreliable.morejs.util.Utils; 4 | import dev.latvian.mods.rhino.util.RemapPrefixForJS; 5 | import net.minecraft.world.item.Item; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.item.Items; 8 | import net.minecraft.world.item.crafting.Ingredient; 9 | import net.minecraft.world.item.trading.MerchantOffer; 10 | 11 | @RemapPrefixForJS("morejs$") 12 | public interface OfferExtension { 13 | 14 | MerchantOffer morejs$self(); 15 | 16 | boolean morejs$isDisabled(); 17 | 18 | void morejs$setDisabled(boolean disabled); 19 | 20 | ItemStack morejs$getFirstCost(); 21 | 22 | void morejs$setFirstCost(ItemStack itemStack); 23 | 24 | ItemStack morejs$getSecondCost(); 25 | 26 | void morejs$setSecondCost(ItemStack itemStack); 27 | 28 | ItemStack morejs$getOutput(); 29 | 30 | void morejs$setOutput(ItemStack itemStack); 31 | 32 | void morejs$setMaxUses(int maxUses); 33 | 34 | void morejs$setDemand(int demand); 35 | 36 | void morejs$setVillagerExperience(int villagerExperience); 37 | 38 | void morejs$setPriceMultiplier(float priceMultiplier); 39 | 40 | void morejs$setRewardExp(boolean rewardExp); 41 | 42 | boolean morejs$isRewardingExp(); 43 | 44 | default void morejs$replaceEmeralds(Item replacement) { 45 | if (morejs$self().getItemCostA().test(new ItemStack(Items.EMERALD))) { 46 | morejs$setFirstCost(new ItemStack(replacement, morejs$getFirstCost().getCount())); 47 | } 48 | 49 | morejs$self().getItemCostB().ifPresent(cost -> { 50 | if (cost.test(new ItemStack(Items.EMERALD))) { 51 | morejs$setSecondCost(new ItemStack(replacement, morejs$getSecondCost().getCount())); 52 | } 53 | }); 54 | 55 | 56 | if (morejs$getOutput().getItem() == Items.EMERALD) { 57 | morejs$setOutput(new ItemStack(replacement, morejs$getOutput().getCount())); 58 | } 59 | } 60 | 61 | default void morejs$replaceItems(Ingredient filter, ItemStack itemStack) { 62 | if (Utils.matchesItemCost(filter, morejs$self().getItemCostA())) { 63 | morejs$setFirstCost(itemStack.copy()); 64 | } 65 | 66 | morejs$self().getItemCostB().ifPresent(cost -> { 67 | if (Utils.matchesItemCost(filter, cost)) { 68 | morejs$setSecondCost(itemStack.copy()); 69 | } 70 | }); 71 | 72 | if (filter.test(morejs$getOutput())) { 73 | morejs$setOutput(itemStack.copy()); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/structure/EntityInfoWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.structure; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.Vec3i; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.nbt.ListTag; 8 | import net.minecraft.util.Mth; 9 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 10 | import net.minecraft.world.phys.Vec3; 11 | 12 | import java.util.List; 13 | import java.util.function.Consumer; 14 | import java.util.function.Predicate; 15 | 16 | public class EntityInfoWrapper { 17 | private final List entities; 18 | private final Vec3i borderSize; 19 | 20 | public EntityInfoWrapper(List entities, Vec3i borderSize) { 21 | this.entities = entities; 22 | this.borderSize = borderSize; 23 | } 24 | 25 | public void forEach(Consumer consumer) { 26 | entities.forEach(consumer); 27 | } 28 | 29 | public void removeIf(Predicate predicate) { 30 | entities.removeIf(predicate); 31 | } 32 | 33 | public void add(CompoundTag tag) { 34 | Preconditions.checkNotNull(tag, "Invalid tag"); 35 | 36 | if (!tag.contains("id")) { 37 | throw new IllegalArgumentException("Invalid tag, missing entity id"); 38 | } 39 | 40 | ListTag motionTag = tag.getList("Motion", 6); 41 | if (motionTag.size() != 3) { 42 | throw new IllegalArgumentException("Invalid or missing tag, `Motion` tag must have 3 entries"); 43 | } 44 | 45 | ListTag rotationTag = tag.getList("Rotation", 5); 46 | if (rotationTag.size() != 2) { 47 | throw new IllegalArgumentException("Invalid or missing tag, `Rotation` tag must have 2 entries"); 48 | } 49 | 50 | ListTag posTag = tag.getList("Pos", 6); 51 | if (posTag.size() != 3) { 52 | throw new IllegalArgumentException("Invalid or missing tag, `Pos` tag must have 3 entries"); 53 | } 54 | 55 | Vec3 pos = new Vec3( 56 | Mth.clamp(posTag.getDouble(0), 0, borderSize.getX()), 57 | Mth.clamp(posTag.getDouble(1), 0, borderSize.getY()), 58 | Mth.clamp(posTag.getDouble(2), 0, borderSize.getZ()) 59 | ); 60 | BlockPos blockPos = new BlockPos(Mth.floor(pos.x), Mth.floor(pos.y), Mth.floor(pos.z)); 61 | this.entities.add(new StructureTemplate.StructureEntityInfo(pos, blockPos, tag)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/EnchantmentTableServerEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import com.almostreliable.morejs.features.villager.IntRange; 4 | import com.google.common.base.Preconditions; 5 | import dev.latvian.mods.kubejs.event.EventResult; 6 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 7 | import net.minecraft.core.BlockPos; 8 | import net.minecraft.core.Holder; 9 | import net.minecraft.core.Registry; 10 | import net.minecraft.core.registries.Registries; 11 | import net.minecraft.resources.ResourceKey; 12 | import net.minecraft.resources.ResourceLocation; 13 | import net.minecraft.world.entity.player.Player; 14 | import net.minecraft.world.item.ItemStack; 15 | import net.minecraft.world.item.enchantment.Enchantment; 16 | import net.minecraft.world.item.enchantment.EnchantmentInstance; 17 | import net.minecraft.world.level.Level; 18 | 19 | import java.util.List; 20 | import java.util.function.BiConsumer; 21 | 22 | public class EnchantmentTableServerEventJS extends EnchantmentTableEventJS { 23 | 24 | protected final EnchantmentMenuState state; 25 | private final BlockPos pos; 26 | private boolean itemChanged; 27 | private final Int2ObjectOpenHashMap enchantments = new Int2ObjectOpenHashMap<>(); 28 | 29 | public EnchantmentTableServerEventJS(ItemStack item, ItemStack secondItem, Level level, BlockPos pos, Player player, EnchantmentMenuState state) { 30 | super(item, secondItem, level, player, state.getMenu()); 31 | this.pos = pos; 32 | this.state = state; 33 | } 34 | 35 | public BlockPos getPosition() { 36 | return pos; 37 | } 38 | 39 | public EnchantmentData get(int index) { 40 | Preconditions.checkElementIndex(index, getSize()); 41 | return enchantments.computeIfAbsent(index, i -> { 42 | List eis = state.getEnchantments(i); 43 | return new EnchantmentData(eis, i, state.getMenu(), getLevel()); 44 | }); 45 | } 46 | 47 | public int getSize() { 48 | return state.getMenu().costs.length; 49 | } 50 | 51 | public void setItem(ItemStack item) { 52 | this.itemChanged = true; 53 | this.item = item; 54 | } 55 | 56 | public boolean itemWasChanged() { 57 | return itemChanged; 58 | } 59 | 60 | @Override 61 | public void afterPosted(EventResult result) { 62 | super.afterPosted(result); 63 | 64 | // If the enchantments are cleared we want also to clear the required level. 65 | enchantments.forEach((integer, data) -> { 66 | if (data.getEnchantments().isEmpty()) { 67 | data.setRequiredLevel(0); 68 | data.clearClue(); 69 | } 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/TreasureMapTrade.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.trades; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeItem; 4 | import com.almostreliable.morejs.util.BlockPosFinder; 5 | import net.minecraft.core.Holder; 6 | import net.minecraft.core.component.DataComponents; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.server.level.ServerLevel; 9 | import net.minecraft.util.RandomSource; 10 | import net.minecraft.world.entity.Entity; 11 | import net.minecraft.world.item.ItemStack; 12 | import net.minecraft.world.item.MapItem; 13 | import net.minecraft.world.item.trading.MerchantOffer; 14 | import net.minecraft.world.level.saveddata.maps.MapDecorationType; 15 | import net.minecraft.world.level.saveddata.maps.MapDecorationTypes; 16 | import net.minecraft.world.level.saveddata.maps.MapItemSavedData; 17 | 18 | import javax.annotation.Nullable; 19 | 20 | public class TreasureMapTrade extends TransformableTrade { 21 | protected final BlockPosFinder blockPosFinder; 22 | @Nullable protected Component displayName; 23 | protected Holder destinationType = MapDecorationTypes.RED_X; 24 | private boolean renderBiomePreviewMap = true; 25 | 26 | private byte mapViewScale = 2; 27 | 28 | public TreasureMapTrade(TradeItem[] inputs, BlockPosFinder blockPosFinder) { 29 | super(inputs); 30 | this.blockPosFinder = blockPosFinder; 31 | } 32 | 33 | public TreasureMapTrade displayName(Component name) { 34 | this.displayName = name; 35 | return this; 36 | } 37 | 38 | public TreasureMapTrade marker(Holder type) { 39 | this.destinationType = type; 40 | return this; 41 | } 42 | 43 | public TreasureMapTrade noPreview() { 44 | this.renderBiomePreviewMap = false; 45 | return this; 46 | } 47 | 48 | public TreasureMapTrade scale(byte scale) { 49 | this.mapViewScale = scale; 50 | return this; 51 | } 52 | 53 | @Override 54 | @Nullable 55 | public MerchantOffer createOffer(Entity trader, RandomSource random) { 56 | if (trader.level() instanceof ServerLevel level) { 57 | var pos = blockPosFinder.findPosition(level, trader); 58 | if (pos == null) return null; 59 | 60 | ItemStack map = MapItem.create(level, pos.getX(), pos.getZ(), this.mapViewScale, true, true); 61 | if (renderBiomePreviewMap) MapItem.renderBiomePreviewMap(level, map); 62 | MapItemSavedData.addTargetDecoration(map, pos, "+", destinationType); 63 | if (displayName != null) { 64 | map.set(DataComponents.CUSTOM_NAME, displayName); 65 | } 66 | 67 | return createOffer(map, random); 68 | } 69 | 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/core/Events.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.core; 2 | 3 | import com.almostreliable.morejs.features.enchantment.EnchantmentTableServerEventJS; 4 | import com.almostreliable.morejs.features.enchantment.EnchantmentTableTooltipEventJS; 5 | import com.almostreliable.morejs.features.enchantment.PlayerEnchantEventJS; 6 | import com.almostreliable.morejs.features.misc.ExperiencePlayerEventJS; 7 | import com.almostreliable.morejs.features.misc.PiglinPlayerBehaviorEventJS; 8 | import com.almostreliable.morejs.features.potion.PotionBrewingRegisterEvent; 9 | import com.almostreliable.morejs.features.structure.StructureAfterPlaceEventJS; 10 | import com.almostreliable.morejs.features.structure.StructureLoadEventJS; 11 | import com.almostreliable.morejs.features.teleport.EntityTeleportsEventJS; 12 | import com.almostreliable.morejs.features.villager.events.*; 13 | import dev.latvian.mods.kubejs.event.EventGroup; 14 | import dev.latvian.mods.kubejs.event.EventHandler; 15 | 16 | public interface Events { 17 | EventGroup GROUP = EventGroup.of("MoreJS"); 18 | EventHandler VILLAGER_TRADING = GROUP.server("villagerTrades", () -> VillagerTradingEventJS.class); 19 | EventHandler WANDERING_TRADING = GROUP.server("wandererTrades", () -> WandererTradingEventJS.class); 20 | EventHandler PLAYER_START_TRADING = GROUP.server("playerStartTrading", () -> StartTradingEventJS.class); 21 | EventHandler UPDATE_OFFER = GROUP.server("updateOffer", 22 | () -> SingleUpdateOfferEventJS.class).hasResult(); 23 | EventHandler POST_UPDATE_OFFERS = GROUP.server("postUpdateOffers", () -> PostUpdateOfferEventJS.class); 24 | EventHandler IS_ENCHANTABLE = GROUP.server("isEnchantable", 25 | () -> EnchantmentTableServerEventJS.class); 26 | EventHandler ENCHANTMENT_TABLE_CHANGED = GROUP.server("enchantmentTableChanged", 27 | () -> EnchantmentTableServerEventJS.class); 28 | EventHandler ENCHANTMENT_TABLE_ENCHANT = GROUP 29 | .server("playerEnchant", () -> PlayerEnchantEventJS.class) 30 | .hasResult(); 31 | EventHandler ENCHANTMENT_TABLE_TOOLTIP = GROUP.client("enchantmentTableTooltip", 32 | () -> EnchantmentTableTooltipEventJS.class); 33 | EventHandler TELEPORT = GROUP.server("teleport", () -> EntityTeleportsEventJS.class).hasResult(); 34 | EventHandler STRUCTURE_LOAD = GROUP.server("structureLoad", () -> StructureLoadEventJS.class); 35 | EventHandler STRUCTURE_AFTER_PLACE = GROUP.server("structureAfterPlace", () -> StructureAfterPlaceEventJS.class); 36 | EventHandler XP_CHANGE = GROUP.server("playerXpChange", () -> ExperiencePlayerEventJS.class).hasResult(); 37 | EventHandler PIGLIN_PLAYER_BEHAVIOR = GROUP.server("piglinPlayerBehavior", () -> PiglinPlayerBehaviorEventJS.class); 38 | EventHandler POTION_BREWING_REGISTER = GROUP.server("registerPotionBrewing", 39 | () -> PotionBrewingRegisterEvent.class); 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Preview 3 |

MoreJS

4 |

5 | 6 |
7 | 8 | A [Minecraft] mod to extend [KubeJS] with additional events. 9 | 10 | [![Workflow Status][workflow_status_badge]][workflow_status_link] 11 | [![License][license_badge]][license] 12 | 13 | [![Version][version_badge]][version_link] 14 | [![Total Downloads CF][total_downloads_cf_badge]][curseforge] 15 | [![Total Downloads MR][total_downloads_mr_badge]][modrinth] 16 | 17 | [![Discord][discord_badge]][discord] 18 | [![Wiki][wiki_badge]][wiki] 19 | 20 |
21 | 22 | ## **📑 Overview** 23 | 24 | This is a mod for [Minecraft] [NeoForge] and needs [KubeJS].
25 | 26 | 27 | ## **🔧 Installation** 28 | 1. Download the latest **mod jar** from the [releases], from [CurseForge] or from [Modrinth]. 29 | 2. Download the latest **mod jar** of [KubeJS]. 30 | 3. Install Minecraft [NeoForge]. 31 | 4. Drop both **jar files** into your mods folder. 32 | 33 | ## **⚙️ More Information** 34 | For more information about the usage and the functionality of the mod, please visit our [wiki]. 35 | 36 | ## **🎓 License** 37 | This project is licensed under the [GNU Lesser General Public License v3.0][license]. 38 | 39 | 40 | [workflow_status_badge]: https://img.shields.io/github/actions/workflow/status/AlmostReliable/morejs/build.yml?branch=1.20.1&style=for-the-badge 41 | [workflow_status_link]: https://github.com/AlmostReliable/morejs/actions 42 | [license_badge]: https://img.shields.io/github/license/AlmostReliable/morejs?style=for-the-badge 43 | [version_badge]: https://img.shields.io/badge/dynamic/json?color=0078FF&label=release&style=for-the-badge&query=name&url=https://api.razonyang.com/v1/github/tag/AlmostReliable/morejs%3Fprefix=v1.20.1- 44 | [version_link]: https://github.com/AlmostReliable/morejs/releases/latest 45 | [total_downloads_cf_badge]: https://img.shields.io/badge/dynamic/json?color=e04e14&label=CurseForge&style=for-the-badge&query=downloads.total&url=https%3A%2F%2Fapi.cfwidget.com%2F666198&logo=curseforge 46 | [total_downloads_mr_badge]: https://img.shields.io/modrinth/dt/mo64mR1W?color=5da545&label=Modrinth&style=for-the-badge&logo=modrinth 47 | [discord_badge]: https://img.shields.io/discord/917251858974789693?color=5865f2&label=Discord&logo=discord&style=for-the-badge 48 | [wiki_badge]: https://img.shields.io/badge/Read%20the-Wiki-ba00ff?style=for-the-badge 49 | 50 | 51 | [minecraft]: https://www.minecraft.net/ 52 | [kubejs]: https://www.curseforge.com/minecraft/mc-mods/kubejs 53 | [discord]: https://discord.com/invite/ThFnwZCyYY 54 | [releases]: https://github.com/AlmostReliable/morejs/releases 55 | [curseforge]: https://www.curseforge.com/minecraft/mc-mods/morejs 56 | [modrinth]: https://modrinth.com/mod/morejs 57 | 58 | [neoforge]: https://neoforged.net/ 59 | 60 | [wiki]: https://docs.almostreliable.com/morejs/ 61 | [changelog]: CHANGELOG.md 62 | [license]: LICENSE 63 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/EnchantmentTableTooltipEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import com.almostreliable.morejs.BuildConfig; 4 | import net.minecraft.core.Registry; 5 | import net.minecraft.core.registries.Registries; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.resources.ResourceKey; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.inventory.EnchantmentMenu; 11 | import net.minecraft.world.item.ItemStack; 12 | import net.minecraft.world.item.enchantment.Enchantment; 13 | import net.minecraft.world.item.enchantment.EnchantmentInstance; 14 | import net.minecraft.world.level.Level; 15 | 16 | import javax.annotation.Nullable; 17 | import java.util.List; 18 | 19 | public class EnchantmentTableTooltipEventJS extends EnchantmentTableEventJS { 20 | private final int slot; 21 | private final List components; 22 | @Nullable EnchantmentInstance clue; 23 | 24 | public EnchantmentTableTooltipEventJS(ItemStack item, ItemStack secondItem, Level level, Player player, EnchantmentMenu menu, int slot, List components) { 25 | super(item, secondItem, level, player, menu); 26 | this.slot = slot; 27 | this.components = components; 28 | } 29 | 30 | public List getComponents() { 31 | return components; 32 | } 33 | 34 | public void removeComponent(int index) { 35 | components.remove(index); 36 | } 37 | 38 | public void clearComponents() { 39 | components.clear(); 40 | } 41 | 42 | public void addComponent(Component component) { 43 | components.add(component); 44 | } 45 | 46 | public void addComponent(int index, Component component) { 47 | components.add(index, component); 48 | } 49 | 50 | public int getSlot() { 51 | return slot; 52 | } 53 | 54 | public int getRequiredLevel() { 55 | return menu.costs[slot]; 56 | } 57 | 58 | public EnchantmentInstance getClue() { 59 | if (clue == null) { 60 | int enchantmentIntId = menu.enchantClue[slot]; 61 | int level = menu.levelClue[slot]; 62 | Registry enchantments = getLevel().registryAccess().registryOrThrow(Registries.ENCHANTMENT); 63 | clue = enchantments 64 | .getHolder(enchantmentIntId) 65 | .map(ref -> new EnchantmentInstance(ref, level)) 66 | .orElseThrow(() -> new IllegalStateException("Enchantment not found for id: " + enchantmentIntId)); 67 | } 68 | 69 | return clue; 70 | } 71 | 72 | public ResourceLocation getClueId() { 73 | return getClue().enchantment 74 | .unwrapKey() 75 | .map(ResourceKey::location) 76 | .orElse(ResourceLocation.fromNamespaceAndPath( 77 | BuildConfig.MOD_ID, "unknown_id")); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/MerchantScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.features.villager.OfferExtension; 4 | import dev.latvian.mods.kubejs.script.ConsoleJS; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 8 | import net.minecraft.client.gui.screens.inventory.MerchantScreen; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.world.entity.player.Inventory; 11 | import net.minecraft.world.inventory.MerchantMenu; 12 | import net.minecraft.world.item.trading.MerchantOffer; 13 | import org.spongepowered.asm.mixin.Final; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Shadow; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.Redirect; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | @Mixin(MerchantScreen.class) 22 | public abstract class MerchantScreenMixin extends AbstractContainerScreen { 23 | 24 | 25 | @Shadow int scrollOff; 26 | @Shadow @Final private MerchantScreen.TradeOfferButton[] tradeOfferButtons; 27 | @Shadow private int shopItem; 28 | 29 | public MerchantScreenMixin(MerchantMenu abstractContainerMenu, Inventory inventory, Component component) { 30 | // IGNORE THIS. 31 | super(abstractContainerMenu, inventory, component); 32 | } 33 | 34 | private static boolean morejs$offerIsDisabled(MerchantOffer offer) { 35 | return ((OfferExtension) offer).morejs$isDisabled(); 36 | } 37 | 38 | @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/trading/MerchantOffer;isOutOfStock()Z", ordinal = 0)) 39 | private void morejs$disableButtonsIfNeeded(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks, CallbackInfo ci) { 40 | try { 41 | var offer = this.menu.getOffers().get(this.shopItem); 42 | if (morejs$offerIsDisabled(offer) && this.isHovering(186, 35, 22, 21, mouseX, mouseY)) { 43 | guiGraphics.renderTooltip(Minecraft.getInstance().font, 44 | Component.literal("You don't meet the requirements to buy this item."), 45 | mouseX, 46 | mouseY); 47 | } 48 | } catch (Exception e) { 49 | ConsoleJS.CLIENT.warn("Error while trying to get the trade offer. Mismatch for index: " + this.shopItem); 50 | } 51 | } 52 | 53 | @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/trading/MerchantOffer;isOutOfStock()Z", ordinal = 0)) 54 | private boolean morejs$renderDeprecatedTooltipOnNotDisabled(MerchantOffer merchantOffer) { 55 | return merchantOffer.isOutOfStock() && !morejs$offerIsDisabled(merchantOffer); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /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 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/TradingManager.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.villager.events.VillagerTradingEventJS; 5 | import com.almostreliable.morejs.features.villager.events.WandererTradingEventJS; 6 | import com.google.common.collect.HashBasedTable; 7 | import com.google.common.collect.Table; 8 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 9 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 10 | import net.minecraft.world.entity.npc.VillagerProfession; 11 | import net.minecraft.world.entity.npc.VillagerTrades; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class TradingManager { 18 | public static void invokeVillagerTradeEvent() { 19 | synchronized (VillagerTrades.TRADES) { 20 | VillagerUtils.CACHED_PROFESSION_TRADES.clear(); 21 | var allTrades = createTradesTable(); 22 | 23 | Events.VILLAGER_TRADING.post(new VillagerTradingEventJS(allTrades)); 24 | 25 | allTrades.rowMap().forEach((profession, tradesPerLevel) -> { 26 | Int2ObjectMap newTrades = new Int2ObjectOpenHashMap<>(); 27 | tradesPerLevel.forEach((level, listings) -> { 28 | var listingsArray = listings.toArray(new VillagerTrades.ItemListing[0]); 29 | newTrades.put(level.intValue(), listingsArray); 30 | }); 31 | 32 | VillagerTrades.TRADES.put(profession, newTrades); 33 | }); 34 | } 35 | } 36 | 37 | private static Table> createTradesTable() { 38 | Table> allTrades = HashBasedTable.create(); 39 | for (var entry : VillagerTrades.TRADES.entrySet()) { 40 | var profession = entry.getKey(); 41 | var trades = entry.getValue(); 42 | trades.forEach((level, listingsArray) -> { 43 | List listings = new ArrayList<>(Arrays.asList(listingsArray)); 44 | allTrades.put(profession, level, listings); 45 | }); 46 | } 47 | 48 | return allTrades; 49 | } 50 | 51 | public static void invokeWanderingTradeEvent() { 52 | synchronized (VillagerTrades.WANDERING_TRADER_TRADES) { 53 | var allTrades = new Int2ObjectOpenHashMap>(); 54 | VillagerTrades.WANDERING_TRADER_TRADES.forEach((integer, itemListings) -> { 55 | allTrades.put(integer.intValue(), new ArrayList<>(Arrays.asList(itemListings))); 56 | }); 57 | 58 | Events.WANDERING_TRADING.post(new WandererTradingEventJS(allTrades)); 59 | 60 | allTrades.forEach((level, listings) -> { 61 | var listingsArray = listings.toArray(new VillagerTrades.ItemListing[0]); 62 | VillagerTrades.WANDERING_TRADER_TRADES.put(level.intValue(), listingsArray); 63 | }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/structure/PaletteWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.structure; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.Vec3i; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.world.level.block.state.BlockState; 8 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 9 | 10 | import javax.annotation.Nullable; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.function.Consumer; 14 | import java.util.function.Predicate; 15 | 16 | public class PaletteWrapper { 17 | private final StructureTemplate.Palette palette; 18 | private final Vec3i borderSize; 19 | private final Map cache = new HashMap<>(); 20 | 21 | public PaletteWrapper(StructureTemplate.Palette palette, Vec3i borderSize) { 22 | this.palette = palette; 23 | this.borderSize = borderSize; 24 | } 25 | 26 | public void clear() { 27 | palette.blocks().clear(); 28 | cache.clear(); 29 | } 30 | 31 | public void add(BlockPos pos, BlockState state) { 32 | add(pos, state, null); 33 | } 34 | 35 | public void add(BlockPos pos, BlockState state, @Nullable CompoundTag tag) { 36 | Preconditions.checkNotNull(pos, "Invalid position"); 37 | Preconditions.checkNotNull(state, "Invalid state"); 38 | Preconditions.checkArgument(0 <= pos.getX() && pos.getX() < borderSize.getX(), 39 | "Invalid position, x must be between 0 and " + borderSize.getX()); 40 | Preconditions.checkArgument(0 <= pos.getX() && pos.getY() < borderSize.getY(), 41 | "Invalid position, y must be between 0 and " + borderSize.getY()); 42 | Preconditions.checkArgument(0 <= pos.getX() && pos.getZ() < borderSize.getZ(), 43 | "Invalid position, z must be between 0 and " + borderSize.getZ()); 44 | 45 | StructureTemplate.StructureBlockInfo info = get(pos); 46 | //noinspection ConstantValue 47 | if (((Object)info) instanceof StructureBlockInfoModification mod) { // Direct cast seems not to work anymore. Have fun with this. 48 | mod.setVanillaBlockState(state); 49 | mod.setNbt(tag); 50 | return; 51 | } 52 | 53 | var newInfo = new StructureTemplate.StructureBlockInfo(pos, state, tag); 54 | palette.blocks().add(newInfo); 55 | cache.put(pos, newInfo); 56 | } 57 | 58 | public void forEach(Consumer consumer) { 59 | palette.blocks().forEach(consumer); 60 | } 61 | 62 | public void removeIf(Predicate predicate) { 63 | palette.blocks().removeIf(block -> { 64 | if (predicate.test(block)) { 65 | cache.remove(block.pos()); 66 | return true; 67 | } 68 | return false; 69 | }); 70 | } 71 | 72 | @Nullable 73 | public StructureTemplate.StructureBlockInfo get(BlockPos pos) { 74 | if (cache.isEmpty()) { 75 | forEach(info -> cache.put(info.pos(), info)); 76 | } 77 | 78 | return cache.get(pos); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/TransformableTrade.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.trades; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeItem; 4 | import com.google.common.base.Preconditions; 5 | import dev.latvian.mods.rhino.util.HideFromJS; 6 | import net.minecraft.util.RandomSource; 7 | import net.minecraft.world.entity.Entity; 8 | import net.minecraft.world.entity.npc.VillagerTrades; 9 | import net.minecraft.world.item.ItemStack; 10 | import net.minecraft.world.item.trading.MerchantOffer; 11 | 12 | import javax.annotation.Nullable; 13 | import java.util.Optional; 14 | 15 | @SuppressWarnings("UnusedReturnValue") 16 | public abstract class TransformableTrade 17 | implements VillagerTrades.ItemListing { 18 | 19 | protected final TradeItem firstInput; 20 | protected final TradeItem secondInput; 21 | protected int maxUses = 16; 22 | protected int villagerExperience = 2; 23 | protected float priceMultiplier = 0.05F; 24 | @Nullable private Transformer transformer; 25 | 26 | public TransformableTrade(TradeItem[] inputs) { 27 | Preconditions.checkArgument(1 <= inputs.length && inputs.length <= 2, "Inputs must be 1 or 2 items"); 28 | this.firstInput = inputs[0]; 29 | this.secondInput = inputs.length == 2 ? inputs[1] : TradeItem.EMPTY; 30 | } 31 | 32 | @Nullable 33 | @Override 34 | public final MerchantOffer getOffer(Entity entity, RandomSource random) { 35 | MerchantOffer offer = createOffer(entity, random); 36 | if (offer == null) { 37 | return null; 38 | } 39 | if (transformer != null) { 40 | transformer.accept(offer, entity, random); 41 | } 42 | return offer; 43 | } 44 | 45 | @HideFromJS 46 | @Nullable 47 | public abstract MerchantOffer createOffer(Entity entity, RandomSource random); 48 | 49 | 50 | public T transform(Transformer offerModification) { 51 | this.transformer = offerModification; 52 | return getSelf(); 53 | } 54 | 55 | public T maxUses(int maxUses) { 56 | this.maxUses = maxUses; 57 | return getSelf(); 58 | } 59 | 60 | public T villagerExperience(int villagerExperience) { 61 | this.villagerExperience = villagerExperience; 62 | return getSelf(); 63 | } 64 | 65 | public T priceMultiplier(float priceMultiplier) { 66 | this.priceMultiplier = priceMultiplier; 67 | return getSelf(); 68 | } 69 | 70 | @SuppressWarnings("unchecked") 71 | protected T getSelf() { 72 | return (T) this; 73 | } 74 | 75 | protected MerchantOffer createOffer(ItemStack output, RandomSource random) { 76 | var fi = firstInput.createItemCost(random); 77 | var si = Optional.ofNullable(secondInput.isEmpty() ? null : secondInput.createItemCost(random)); 78 | return new MerchantOffer(fi, si, output, maxUses, villagerExperience, priceMultiplier); 79 | } 80 | 81 | public TradeItem getFirstInput() { 82 | return firstInput; 83 | } 84 | 85 | public TradeItem getSecondInput() { 86 | return secondInput; 87 | } 88 | 89 | public interface Transformer { 90 | void accept(MerchantOffer offer, Entity entity, RandomSource random); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example_scripts/trading.js: -------------------------------------------------------------------------------- 1 | MoreJS.villagerTrades((event) => { 2 | event.removeVanillaTypedTrades(["minecraft:farmer"], 2); 3 | }); 4 | 5 | // Check trade filter 6 | MoreJS.villagerTrades((event) => { 7 | event.removeTrades({ 8 | first: "#c:crops/wheat", 9 | outputCount: [0, 32], 10 | level: 1, 11 | professions: "minecraft:farmer", 12 | }); 13 | }); 14 | 15 | MoreJS.villagerTrades((event) => { 16 | event.addTrade("minecraft:farmer", 2, Item.of("minecraft:diamond", 10), "minecraft:stick"); 17 | event.addTrade("minecraft:farmer", 2, [Item.of("minecraft:diamond", 10), "minecraft:emerald"], "minecraft:stick"); 18 | }); 19 | 20 | MoreJS.villagerTrades(event => { 21 | const trade = VillagerUtils.createCustomMapTrade(["10x minecraft:diamond", "minecraft:paper"], (level, entity) => { 22 | const rndBiome = Registry.of("worldgen/biome").getValues("#minecraft:is_overworld").getRandom() 23 | return MoreUtils.findBiome(entity.blockPosition(), level, rndBiome, 250); 24 | }) 25 | 26 | event.addTrade("minecraft:cartographer", 1, trade); 27 | }) 28 | 29 | MoreJS.updateOffer((event) => { 30 | if (event.offer.firstCost.id === "minecraft:beetroot") { 31 | console.log("Blocked beetroot!"); 32 | return event.cancel(); 33 | } 34 | 35 | event.offer.replaceItems("minecraft:potato", "minecraft:nether_star"); 36 | }); 37 | 38 | MoreJS.updateOffer((event) => { 39 | // In our example we will remove the clay trade from the mason and replace it 40 | // with a trade from the shepherd 41 | if (event.isProfession("minecraft:mason") && event.offer.firstCost.id === "minecraft:clay_ball") { 42 | // Get all level 2 trades from shepherd 43 | const shepherdTrades = event.getVillagerTrades("minecraft:shepherd", 2); 44 | 45 | // Now create a random offer from these trades 46 | const newOffer = event.createRandomOffer(shepherdTrades); 47 | 48 | // Set the new offer, which will override the coal offer 49 | event.setOffer(newOffer); 50 | } 51 | }); 52 | 53 | MoreJS.updateOffer((event) => { 54 | if (event.isProfession("minecraft:cartographer") && event.random.nextDouble() < 0.2) { 55 | const randomBiome = Registry.of("worldgen/biome").getValues("#minecraft:is_savanna").getRandom(); 56 | const trade = VillagerUtils.createBiomeMapTrade("minecraft:emerald_block", randomBiome).displayName( 57 | "Random savanna biome" 58 | ); 59 | event.setOffer(trade); 60 | } 61 | }); 62 | 63 | MoreJS.updateOffer((event) => { 64 | if (!event.isProfession("farmer")) { 65 | return; 66 | } 67 | 68 | const ItemAttributeModifiers = Java.loadClass("net.minecraft.world.item.component.ItemAttributeModifiers"); 69 | 70 | const attributes = ItemAttributeModifiers.builder() 71 | .add( 72 | "minecraft:generic.attack_damage", 73 | { 74 | id: "minecraft:base_attack_damage", 75 | operation: "add_value", 76 | amount: 4.0, 77 | }, 78 | "mainhand" 79 | ) 80 | .build(); 81 | const item = Item.of("minecraft:stick").set("minecraft:attribute_modifiers", attributes); 82 | event.offer.output = item; 83 | }); 84 | 85 | MoreJS.postUpdateOffers(event => { 86 | event.addTrade(VillagerUtils.createSimpleTrade("minecraft:emerald_block", "minecraft:nether_star")); 87 | }) 88 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/TradeMatcher.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import net.minecraft.core.registries.BuiltInRegistries; 4 | import net.minecraft.world.entity.npc.VillagerProfession; 5 | import net.minecraft.world.item.ItemStack; 6 | import net.minecraft.world.item.crafting.Ingredient; 7 | import net.minecraft.world.item.trading.ItemCost; 8 | 9 | import javax.annotation.Nullable; 10 | import java.util.Optional; 11 | 12 | public record TradeMatcher(TradeFilter filter, OnMatch onMatch) { 13 | 14 | public boolean matchMerchantLevel(int level) { 15 | if (filter.level().isEmpty()) { 16 | return true; 17 | } 18 | 19 | return filter.level().get().test(level); 20 | } 21 | 22 | public boolean matchProfession(VillagerProfession profession) { 23 | if (filter.professions().isEmpty()) { 24 | return true; 25 | } 26 | 27 | var holder = BuiltInRegistries.VILLAGER_PROFESSION.wrapAsHolder(profession); 28 | return filter.professions().get().contains(holder); 29 | } 30 | 31 | public boolean matchType(TradeTypes type) { 32 | if (filter.types().isEmpty()) { 33 | return true; 34 | } 35 | 36 | return filter.types().get().contains(type); 37 | } 38 | 39 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 40 | private boolean match(Optional filter, Optional countFilter, ItemStack itemStack) { 41 | var matchesCount = countFilter.isEmpty() || countFilter.get().test(itemStack.getCount()); 42 | var matchesFilter = filter.isEmpty() || filter.get().test(itemStack); 43 | return matchesCount && matchesFilter; 44 | } 45 | 46 | public boolean match(ItemStack costA, ItemStack costB, ItemStack output, TradeTypes type) { 47 | boolean firstMatch = match(filter.first(), filter.firstCount(), costA); 48 | boolean secondMatch = match(filter.second(), filter.secondCount(), costB); 49 | boolean outputMatch = match(filter.output(), filter.outputCount(), output); 50 | boolean matched = matchType(type) && firstMatch && secondMatch && outputMatch; 51 | if (matched) { 52 | onMatch.notify(costA, costB, output); 53 | } 54 | 55 | return matched; 56 | } 57 | 58 | public boolean match(ItemStack first, ItemStack output, TradeTypes type) { 59 | return match(first, ItemStack.EMPTY, output, type); 60 | } 61 | 62 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 63 | public boolean match(ItemCost costA, Optional costB, ItemStack output, TradeTypes type) { 64 | return match(costA.itemStack(), costB.map(ItemCost::itemStack).orElse(ItemStack.EMPTY), output, type); 65 | } 66 | 67 | public boolean match(ItemStack costA, ItemCost costB, ItemStack output, TradeTypes type) { 68 | return match(costA, costB.itemStack(), output, type); 69 | } 70 | 71 | public boolean match(ItemCost costA, ItemStack output, TradeTypes type) { 72 | return match(costA, Optional.empty(), output, type); 73 | } 74 | 75 | public interface Filterable { 76 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") // Fuck of jetbrains 77 | default boolean matchesTradeFilter(TradeMatcher filter) { 78 | // default behavior given, as this interface is used in a mixin and not every class will implement it. 79 | return false; 80 | } 81 | } 82 | 83 | public interface OnMatch { 84 | void notify(ItemStack first, @Nullable ItemStack second, ItemStack output); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/trades/PotionTrade.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.trades; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeItem; 4 | import dev.latvian.mods.kubejs.script.ConsoleJS; 5 | import net.minecraft.core.Holder; 6 | import net.minecraft.core.component.DataComponents; 7 | import net.minecraft.core.registries.BuiltInRegistries; 8 | import net.minecraft.util.RandomSource; 9 | import net.minecraft.world.entity.Entity; 10 | import net.minecraft.world.item.Item; 11 | import net.minecraft.world.item.ItemStack; 12 | import net.minecraft.world.item.Items; 13 | import net.minecraft.world.item.alchemy.Potion; 14 | import net.minecraft.world.item.alchemy.PotionBrewing; 15 | import net.minecraft.world.item.alchemy.PotionContents; 16 | import net.minecraft.world.item.trading.MerchantOffer; 17 | 18 | import javax.annotation.Nullable; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.Objects; 22 | import java.util.stream.Stream; 23 | 24 | public class PotionTrade extends TransformableTrade { 25 | 26 | @Nullable 27 | List> potions; 28 | private Item itemForPotion; 29 | private boolean onlyBrewablePotion; 30 | private boolean noBrewablePotion; 31 | 32 | public PotionTrade(TradeItem[] inputs) { 33 | super(inputs); 34 | this.itemForPotion = Items.POTION; 35 | } 36 | 37 | public PotionTrade item(Item item) { 38 | this.itemForPotion = item; 39 | return this; 40 | } 41 | 42 | public PotionTrade potions(Potion... potions) { 43 | this.potions = Arrays.stream(potions).peek(e -> { 44 | if (e == null) { 45 | ConsoleJS.SERVER.error("Null potion in array: " + Arrays.toString(potions)); 46 | } 47 | }).filter(Objects::nonNull).map(BuiltInRegistries.POTION::wrapAsHolder).toList(); 48 | 49 | return this; 50 | } 51 | 52 | public PotionTrade onlyBrewablePotion() { 53 | this.onlyBrewablePotion = true; 54 | return this; 55 | } 56 | 57 | public PotionTrade noBrewablePotion() { 58 | this.noBrewablePotion = false; 59 | return this; 60 | } 61 | 62 | private List> getFilteredPotions(PotionBrewing potionBrewing, Stream> potions) { 63 | return potions.filter(potionHolder -> { 64 | if (potionHolder.value().getEffects().isEmpty()) { 65 | return false; 66 | } 67 | 68 | if (this.onlyBrewablePotion) { 69 | return potionBrewing.isBrewablePotion(potionHolder); 70 | } 71 | 72 | if (this.noBrewablePotion) { 73 | return !potionBrewing.isBrewablePotion(potionHolder); 74 | } 75 | 76 | return true; 77 | }).toList(); 78 | } 79 | 80 | @Nullable 81 | @Override 82 | public MerchantOffer createOffer(Entity entity, RandomSource random) { 83 | var potionBrewing = entity.level().potionBrewing(); 84 | var allowedPotions = 85 | potions == null ? getFilteredPotions(potionBrewing, BuiltInRegistries.POTION.holders()) 86 | : getFilteredPotions(potionBrewing, potions.stream()); 87 | 88 | if (allowedPotions.isEmpty()) { 89 | return null; 90 | } 91 | 92 | var potion = allowedPotions.get(random.nextInt(potions.size())); 93 | ItemStack itemStack = new ItemStack(itemForPotion); 94 | itemStack.set(DataComponents.POTION_CONTENTS, new PotionContents(potion)); 95 | return createOffer(itemStack, random); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/MoreJSBinding.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs; 2 | 3 | import com.almostreliable.morejs.features.villager.IntRange; 4 | import com.almostreliable.morejs.features.villager.TradeItem; 5 | import com.almostreliable.morejs.util.Utils; 6 | import com.almostreliable.morejs.util.WeightedList; 7 | import com.mojang.datafixers.util.Pair; 8 | import dev.latvian.mods.kubejs.item.ItemStackJS; 9 | import dev.latvian.mods.kubejs.util.RegistryAccessContainer; 10 | import dev.latvian.mods.kubejs.util.UtilsJS; 11 | import net.minecraft.core.BlockPos; 12 | import net.minecraft.core.Holder; 13 | import net.minecraft.core.HolderSet; 14 | import net.minecraft.server.level.ServerLevel; 15 | import net.minecraft.world.level.biome.Biome; 16 | import net.minecraft.world.level.levelgen.structure.Structure; 17 | 18 | import javax.annotation.Nullable; 19 | import java.util.List; 20 | 21 | public class MoreJSBinding { 22 | @Nullable 23 | public static BlockPos findStructure(BlockPos position, ServerLevel level, HolderSet structures, int chunkRadius) { 24 | var result = level 25 | .getChunkSource() 26 | .getGenerator() 27 | .findNearestMapStructure(level, structures, position, chunkRadius, true); 28 | if (result == null) { 29 | return null; 30 | } 31 | 32 | return result.getFirst(); 33 | } 34 | 35 | @Nullable 36 | public static BlockPos findBiome(BlockPos position, ServerLevel level, HolderSet biomes, int chunkRadius) { 37 | Pair> nearestBiome = level.findClosestBiome3d(biomes::contains, 38 | position, 39 | chunkRadius * 16, 40 | 32, 41 | 64); 42 | if (nearestBiome != null) { 43 | return nearestBiome.getFirst(); 44 | } 45 | 46 | return null; 47 | } 48 | 49 | public static WeightedList.Builder weightedList() { 50 | return new WeightedList.Builder<>(); 51 | } 52 | 53 | public static IntRange range(@Nullable Object o) { 54 | if (o instanceof Number number) { 55 | return new IntRange(number.intValue()); 56 | } 57 | 58 | if (o instanceof List list) { 59 | return switch (list.size()) { 60 | case 0 -> IntRange.all(); 61 | case 1 -> range(list.get(0)); 62 | default -> new IntRange(UtilsJS.parseInt(list.get(0), 1), UtilsJS.parseInt(list.get(1), 5)); 63 | }; 64 | } 65 | 66 | return IntRange.all(); 67 | } 68 | 69 | public static WeightedList ofWeightedList(@Nullable Object o) { 70 | if (o instanceof WeightedList.Builder b) { 71 | //noinspection unchecked 72 | return b.build(); 73 | } 74 | 75 | if (o instanceof WeightedList) { 76 | return Utils.cast(o); 77 | } 78 | 79 | var builder = new WeightedList.Builder<>(); 80 | 81 | for (Object entry : Utils.asList(o)) { 82 | List weightValue = Utils.asList(entry); 83 | if (weightValue.size() == 2) { 84 | builder.add(UtilsJS.parseInt(weightValue.get(0), 1), weightValue.get(1)); 85 | } else { 86 | builder.add(1, entry); 87 | } 88 | } 89 | return builder.build(); 90 | } 91 | 92 | public static TradeItem ofTradeItem(RegistryAccessContainer registries, @Nullable Object o) { 93 | if (o instanceof TradeItem item) { 94 | return item; 95 | } 96 | 97 | return TradeItem.of(ItemStackJS.wrap(registries, o)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/entity/PiglinSpecificSensorMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.entity; 2 | 3 | import com.almostreliable.morejs.core.Events; 4 | import com.almostreliable.morejs.features.misc.PiglinPlayerBehaviorEventJS; 5 | import net.minecraft.server.level.ServerLevel; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraft.world.entity.Mob; 8 | import net.minecraft.world.entity.ai.Brain; 9 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 10 | import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; 11 | import net.minecraft.world.entity.ai.sensing.PiglinSpecificSensor; 12 | import net.minecraft.world.entity.monster.hoglin.Hoglin; 13 | import net.minecraft.world.entity.monster.piglin.AbstractPiglin; 14 | import net.minecraft.world.entity.monster.piglin.Piglin; 15 | import net.minecraft.world.entity.player.Player; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Unique; 18 | import org.spongepowered.asm.mixin.injection.At; 19 | import org.spongepowered.asm.mixin.injection.Inject; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 22 | 23 | import java.util.Iterator; 24 | import java.util.List; 25 | import java.util.Optional; 26 | 27 | @Mixin(PiglinSpecificSensor.class) 28 | public class PiglinSpecificSensorMixin { 29 | 30 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 31 | @Unique 32 | private Optional targetablePlayer = Optional.empty(); 33 | @Unique private boolean ignoreHoldingCheck; 34 | @Unique private boolean fired; 35 | 36 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 37 | @Inject(method = "doTick", at = @At(value = "INVOKE", target = "Ljava/util/Optional;isEmpty()Z", ordinal = 4, shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) 38 | private void morejs$doTick( 39 | ServerLevel level, LivingEntity entity, CallbackInfo ci, 40 | Brain brain, Optional nearVisNemesis, 41 | Optional nearVisHuntableHoglin, Optional nearVisBabyHoglin, 42 | Optional nearVisBabyPiglin, Optional nearVisZombiefied, 43 | Optional playerNotWearingGoldArmor, Optional playerHoldingWantedItem, 44 | int i, List nearVisAdultPiglins, List nearAdultPiglins, 45 | NearestVisibleLivingEntities nearEntities, Iterator iterator, LivingEntity nearEntity) { 46 | if (!(entity instanceof Piglin piglinEntity) 47 | || !(nearEntity instanceof Player player) 48 | || !entity.canAttack(player)) { 49 | return; 50 | } 51 | 52 | var event = new PiglinPlayerBehaviorEventJS(piglinEntity, player, playerNotWearingGoldArmor); 53 | Events.PIGLIN_PLAYER_BEHAVIOR.post(event); 54 | fired = true; 55 | 56 | targetablePlayer = switch (event.getBehavior()) { 57 | case ATTACK -> Optional.of(event.getPlayer()); 58 | case IGNORE -> Optional.empty(); 59 | case KEEP -> playerNotWearingGoldArmor; 60 | }; 61 | 62 | ignoreHoldingCheck = event.isIgnoreHoldingCheck(); 63 | } 64 | 65 | @Inject(method = "doTick", at = @At("TAIL"), locals = LocalCapture.CAPTURE_FAILHARD) 66 | private void morejs$setTarget(ServerLevel level, LivingEntity entity, CallbackInfo ci, Brain brain) { 67 | if (fired) { 68 | brain.setMemory(MemoryModuleType.NEAREST_TARGETABLE_PLAYER_NOT_WEARING_GOLD, targetablePlayer); 69 | if (ignoreHoldingCheck) { 70 | brain.setMemory(MemoryModuleType.NEAREST_PLAYER_HOLDING_WANTED_ITEM, Optional.empty()); 71 | } 72 | 73 | fired = false; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/structure/StructureBlockInfoMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.structure; 2 | 3 | import com.almostreliable.morejs.features.structure.StructureBlockInfoModification; 4 | import com.almostreliable.morejs.util.Utils; 5 | import dev.latvian.mods.rhino.util.HideFromJS; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.core.registries.BuiltInRegistries; 8 | import net.minecraft.nbt.CompoundTag; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.world.level.block.Block; 11 | import net.minecraft.world.level.block.state.BlockState; 12 | import net.minecraft.world.level.block.state.properties.Property; 13 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 14 | import org.spongepowered.asm.mixin.Final; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Mutable; 17 | import org.spongepowered.asm.mixin.Shadow; 18 | 19 | import javax.annotation.Nullable; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | @Mixin(StructureTemplate.StructureBlockInfo.class) 24 | public class StructureBlockInfoMixin implements StructureBlockInfoModification { 25 | 26 | @Mutable @Shadow @Final private CompoundTag nbt; 27 | 28 | @Mutable @Shadow @Final private BlockState state; 29 | 30 | @Shadow @Final public BlockPos pos; 31 | 32 | @Override 33 | public String getId() { 34 | return BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString(); 35 | } 36 | 37 | @Override 38 | public Block getBlock() { 39 | return state.getBlock(); 40 | } 41 | 42 | @Override 43 | public void setBlock(ResourceLocation id) { 44 | Block block = BuiltInRegistries.BLOCK 45 | .getOptional(id) 46 | .orElseThrow(() -> new IllegalArgumentException("Block not found: " + id)); 47 | state = block.defaultBlockState(); 48 | } 49 | 50 | @Override 51 | public void setBlock(ResourceLocation id, Map properties) { 52 | setBlock(id); 53 | if (properties.isEmpty()) { 54 | return; 55 | } 56 | 57 | this.state = getBlock().defaultBlockState(); 58 | for (Property property : this.state.getProperties()) { 59 | Object newValue = properties.get(property.getName()); 60 | if (newValue == null) continue; 61 | 62 | try { 63 | this.state = this.state.setValue(property, Utils.cast(newValue)); 64 | } catch (Exception ignored) { 65 | property.getValue(newValue.toString()).ifPresent(v -> { 66 | this.state = this.state.setValue(property, Utils.cast(v)); 67 | }); 68 | } 69 | } 70 | } 71 | 72 | @Override 73 | public Map getProperties() { 74 | Map properties = new HashMap<>(); 75 | 76 | for (Property property : state.getProperties()) { 77 | Object value = state.getValue(property); 78 | properties.put(property.getName(), value); 79 | } 80 | 81 | return properties; 82 | } 83 | 84 | @Override 85 | public boolean hasNbt() { 86 | return this.nbt != null; 87 | } 88 | 89 | @Override 90 | @Nullable 91 | public CompoundTag getNbt() { 92 | return this.nbt; 93 | } 94 | 95 | @Override 96 | public void setNbt(@Nullable CompoundTag nbt) { 97 | this.nbt = nbt; 98 | } 99 | 100 | @Override 101 | public BlockPos getPosition() { 102 | return this.pos; 103 | } 104 | 105 | @HideFromJS 106 | @Override 107 | public void setVanillaBlockState(BlockState state) { 108 | this.state = state; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/events/UpdateOfferEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.events; 2 | 3 | import com.almostreliable.morejs.features.villager.VillagerUtils; 4 | import dev.latvian.mods.kubejs.entity.KubeLivingEntityEvent; 5 | import net.minecraft.util.RandomSource; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraft.world.entity.npc.*; 8 | import net.minecraft.world.item.trading.MerchantOffer; 9 | import net.minecraft.world.item.trading.MerchantOffers; 10 | 11 | import javax.annotation.Nullable; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | public abstract class UpdateOfferEventJS implements KubeLivingEntityEvent { 17 | 18 | private final AbstractVillager villager; 19 | private final MerchantOffers allOffers; 20 | @Nullable private List cachedWandererTrades; 21 | 22 | public UpdateOfferEventJS(AbstractVillager villager, MerchantOffers allOffers) { 23 | this.villager = villager; 24 | this.allOffers = allOffers; 25 | } 26 | 27 | public RandomSource getRandom() { 28 | return villager.getRandom(); 29 | } 30 | 31 | @Override 32 | public LivingEntity getEntity() { 33 | return villager; 34 | } 35 | 36 | @Nullable 37 | public VillagerData getVillagerData() { 38 | if (villager instanceof VillagerDataHolder v) { 39 | return v.getVillagerData(); 40 | } 41 | 42 | return null; 43 | } 44 | 45 | public boolean isProfession(VillagerProfession profession) { 46 | if (villager instanceof VillagerDataHolder v) { 47 | return v.getVillagerData().getProfession() == profession; 48 | 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public int getVillagerLevel() { 55 | if (villager instanceof VillagerDataHolder v) { 56 | return v.getVillagerData().getLevel(); 57 | } 58 | 59 | return -1; 60 | } 61 | 62 | public VillagerProfession getProfession() { 63 | if (villager instanceof VillagerDataHolder v) { 64 | return v.getVillagerData().getProfession(); 65 | } 66 | 67 | return VillagerProfession.NONE; 68 | } 69 | 70 | public boolean isVillager() { 71 | return villager instanceof Villager; 72 | } 73 | 74 | public boolean isWanderer() { 75 | return villager instanceof WanderingTrader; 76 | } 77 | 78 | public boolean isUnknownTrader() { 79 | return !isVillager() && !isWanderer(); 80 | } 81 | 82 | public MerchantOffers getAllOffers() { 83 | return allOffers; 84 | } 85 | 86 | @Nullable 87 | public MerchantOffer createRandomOffer(List possibleTrades) { 88 | if (possibleTrades.isEmpty()) { 89 | return null; 90 | } 91 | 92 | int i = getLevel().getRandom().nextInt(possibleTrades.size()); 93 | VillagerTrades.ItemListing randomListing = possibleTrades.get(i); 94 | return randomListing.getOffer(getEntity(), getLevel().getRandom()); 95 | } 96 | 97 | public List getVillagerTrades(VillagerProfession profession) { 98 | return VillagerUtils.getVillagerTrades(profession); 99 | } 100 | 101 | public List getVillagerTrades(VillagerProfession profession, int level) { 102 | return VillagerUtils.getVillagerTrades(profession, level); 103 | } 104 | 105 | public List getWandererTrades() { 106 | if (cachedWandererTrades == null) { 107 | cachedWandererTrades = new ArrayList<>(); 108 | for (VillagerTrades.ItemListing[] listings : VillagerTrades.WANDERING_TRADER_TRADES.values()) { 109 | cachedWandererTrades.addAll(Arrays.asList(listings)); 110 | } 111 | } 112 | 113 | return cachedWandererTrades; 114 | } 115 | 116 | public List getWandererTrades(int level) { 117 | return VillagerUtils.getWandererTrades(level); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/test/java/testmod/tests/PotionBrewingTests.java: -------------------------------------------------------------------------------- 1 | package testmod.tests; 2 | 3 | import com.almostreliable.morejs.BuildConfig; 4 | import net.minecraft.gametest.framework.GameTest; 5 | import net.minecraft.gametest.framework.GameTestAssertException; 6 | import net.minecraft.gametest.framework.GameTestHelper; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.item.Items; 9 | import net.minecraft.world.item.alchemy.PotionBrewing; 10 | import net.minecraft.world.item.alchemy.PotionContents; 11 | import net.minecraft.world.item.alchemy.Potions; 12 | import net.neoforged.neoforge.gametest.GameTestHolder; 13 | import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; 14 | import testmod.GameTestTemplates; 15 | 16 | @GameTestHolder(value = BuildConfig.MOD_ID) 17 | @PrefixGameTestTemplate(false) 18 | public class PotionBrewingTests { 19 | 20 | @GameTest(template = GameTestTemplates.EMPTY) 21 | public void removeVanillaSplashContainer(GameTestHelper helper) { 22 | helper.succeedIf(() -> { 23 | if (helper.getLevel().potionBrewing().isInput(Items.SPLASH_POTION.getDefaultInstance())) { 24 | throw new GameTestAssertException("Expected that splash potion container was removed!"); 25 | } 26 | 27 | // We still want to check lingering still exist 28 | if (!helper.getLevel().potionBrewing().isInput(Items.LINGERING_POTION.getDefaultInstance())) { 29 | throw new GameTestAssertException("Expected that lingering potion container still exists!"); 30 | } 31 | }); 32 | } 33 | 34 | @GameTest(template = GameTestTemplates.EMPTY) 35 | public void addCustomContainer(GameTestHelper helper) { 36 | helper.succeedIf(() -> { 37 | PotionBrewing potionBrewing = helper.getLevel().potionBrewing(); 38 | if (!potionBrewing.hasContainerMix(Items.LINGERING_POTION.getDefaultInstance(), 39 | Items.APPLE.getDefaultInstance())) { 40 | throw new GameTestAssertException("Expected that splash potion container was removed!"); 41 | } 42 | }); 43 | } 44 | 45 | @GameTest(template = GameTestTemplates.EMPTY) 46 | public void addPotionBrewing(GameTestHelper helper) { 47 | helper.succeedIf(() -> { 48 | PotionBrewing potionBrewing = helper.getLevel().potionBrewing(); 49 | if (!potionBrewing.hasContainerMix(Items.LINGERING_POTION.getDefaultInstance(), 50 | Items.APPLE.getDefaultInstance())) { 51 | throw new GameTestAssertException("Test doesn't match condition"); 52 | } 53 | }); 54 | } 55 | 56 | @GameTest(template = GameTestTemplates.EMPTY) 57 | public void removePotionBrewing(GameTestHelper helper) { 58 | helper.succeedIf(() -> { 59 | PotionBrewing potionBrewing = helper.getLevel().potionBrewing(); 60 | ItemStack ingredientItem = Items.GLOWSTONE_DUST.getDefaultInstance(); 61 | ItemStack input = PotionContents.createItemStack(Items.POTION, Potions.HARMING); 62 | if (potionBrewing.hasPotionMix(input, ingredientItem)) { 63 | throw new GameTestAssertException("Test doesn't match condition!"); 64 | } 65 | }); 66 | } 67 | 68 | @GameTest(template = GameTestTemplates.EMPTY) 69 | public void addCustomBrewing(GameTestHelper helper) { 70 | helper.succeedIf(() -> { 71 | PotionBrewing potionBrewing = helper.getLevel().potionBrewing(); 72 | ItemStack ingredientItem = Items.STICK.getDefaultInstance(); 73 | ItemStack input = Items.OAK_LOG.getDefaultInstance(); 74 | if (!potionBrewing.hasMix(input, ingredientItem)) { 75 | throw new GameTestAssertException("Test doesn't match condition!"); 76 | } 77 | }); 78 | } 79 | 80 | @GameTest(template = GameTestTemplates.EMPTY) 81 | public void removeCustomBrewing(GameTestHelper helper) { 82 | helper.succeedIf(() -> { 83 | PotionBrewing potionBrewing = helper.getLevel().potionBrewing(); 84 | ItemStack ingredientItem = Items.EMERALD.getDefaultInstance(); 85 | ItemStack input = Items.NETHER_STAR.getDefaultInstance(); 86 | if (potionBrewing.hasMix(input, ingredientItem)) { 87 | throw new GameTestAssertException("Test doesn't match condition!"); 88 | } 89 | }); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/EnchantmentMenuState.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import com.almostreliable.morejs.Debug; 4 | import com.almostreliable.morejs.MoreJS; 5 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 6 | import net.minecraft.core.Holder; 7 | import net.minecraft.world.entity.player.Player; 8 | import net.minecraft.world.inventory.EnchantmentMenu; 9 | import net.minecraft.world.item.ItemStack; 10 | import net.minecraft.world.item.enchantment.Enchantment; 11 | import net.minecraft.world.item.enchantment.EnchantmentInstance; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | public class EnchantmentMenuState { 18 | private final EnchantmentMenu menu; 19 | private final Int2ObjectOpenHashMap> enchantments = new Int2ObjectOpenHashMap<>(); 20 | private final Player player; 21 | private boolean freezeBroadcast = false; 22 | /** 23 | * Approach to fix changing the slot triggers multiple {@link EnchantmentMenu#slotsChanged}... Mojang pls... 24 | */ 25 | private ItemStack currentItem = ItemStack.EMPTY; 26 | private Boolean itemIsEnchantable = null; 27 | private EnchantmentState state = EnchantmentState.IDLE; 28 | 29 | public EnchantmentMenuState(EnchantmentMenu menu, Player player) { 30 | this.menu = menu; 31 | this.player = player; 32 | } 33 | 34 | public boolean isFreezeBroadcast() { 35 | return freezeBroadcast; 36 | } 37 | 38 | public void setFreezeBroadcast(boolean freezeBroadcast) { 39 | this.freezeBroadcast = freezeBroadcast; 40 | } 41 | 42 | public boolean matchesCurrentItem(ItemStack item) { 43 | return ItemStack.matches(currentItem, item); 44 | } 45 | 46 | public void setCurrentItem(ItemStack currentItem) { 47 | this.currentItem = currentItem.copy(); 48 | if (currentItem.isEmpty()) { 49 | itemIsEnchantable = null; 50 | } 51 | } 52 | 53 | public void clearEnchantments() { 54 | enchantments.clear(); 55 | } 56 | 57 | public boolean storeItemIsEnchantable(boolean itemIsEnchantable) { 58 | this.itemIsEnchantable = itemIsEnchantable; 59 | return this.itemIsEnchantable; 60 | } 61 | 62 | public void setEnchantments(int index, List enchantments) { 63 | if (Debug.ENCHANTMENT) { 64 | var s = enchantments.stream().map(ei -> { 65 | var key = ei.enchantment instanceof Holder.Reference ref ? ref.key().location().toString() 66 | : ""; 67 | var level = ei.level; 68 | return String.format("%s ", key, level); 69 | }).collect(Collectors.joining(", ")); 70 | MoreJS.LOG.warn("Setting enchantments for index {} [{}] <{}>", index, s, player); 71 | } 72 | 73 | this.enchantments.put(index, new ArrayList<>(enchantments)); 74 | } 75 | 76 | public List getEnchantments(int index) { 77 | return this.enchantments.computeIfAbsent(index, $ -> new ArrayList<>()); 78 | } 79 | 80 | public EnchantmentState getState() { 81 | return state; 82 | } 83 | 84 | public void setState(EnchantmentState storeEnchantments) { 85 | this.state = storeEnchantments; 86 | } 87 | 88 | public EnchantmentMenu getMenu() { 89 | return menu; 90 | } 91 | 92 | public Player getPlayer() { 93 | return player; 94 | } 95 | 96 | public void prepareEvent(ItemStack item) { 97 | if (Debug.ENCHANTMENT) { 98 | MoreJS.LOG.warn("Prepare enchantment state for item <{}> with player <{}>", item, player); 99 | } 100 | 101 | setCurrentItem(item); 102 | clearEnchantments(); 103 | setFreezeBroadcast(true); 104 | setState(EnchantmentState.STORE_ENCHANTMENTS); 105 | } 106 | 107 | public void reset(ItemStack item) { 108 | if (Debug.ENCHANTMENT) { 109 | MoreJS.LOG.warn("RESETTING enchantment state for item <{}> with player <{}>", item, player); 110 | } 111 | 112 | setCurrentItem(item); 113 | clearEnchantments(); 114 | itemIsEnchantable = null; 115 | setState(EnchantmentState.IDLE); 116 | } 117 | 118 | public ItemStack getCurrentItem() { 119 | return currentItem; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/enchantment/EnchantmentData.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.enchantment; 2 | 3 | import com.almostreliable.morejs.features.villager.IntRange; 4 | import net.minecraft.core.Holder; 5 | import net.minecraft.core.Registry; 6 | import net.minecraft.core.registries.Registries; 7 | import net.minecraft.resources.ResourceKey; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.world.inventory.EnchantmentMenu; 10 | import net.minecraft.world.item.enchantment.Enchantment; 11 | import net.minecraft.world.item.enchantment.EnchantmentInstance; 12 | import net.minecraft.world.level.Level; 13 | 14 | import javax.annotation.Nullable; 15 | import java.util.List; 16 | import java.util.Objects; 17 | import java.util.function.BiPredicate; 18 | 19 | public class EnchantmentData { 20 | 21 | private final List enchantments; 22 | private final int dataSlotIndex; 23 | private final EnchantmentMenu menu; 24 | private final Level level; 25 | 26 | public EnchantmentData(List enchantments, int dataSlotIndex, EnchantmentMenu menu, Level level) { 27 | this.enchantments = enchantments; 28 | this.dataSlotIndex = dataSlotIndex; 29 | this.menu = menu; 30 | this.level = level; 31 | } 32 | 33 | public int getRequiredLevel() { 34 | return menu.costs[dataSlotIndex]; 35 | } 36 | 37 | public void setRequiredLevel(int level) { 38 | menu.costs[dataSlotIndex] = level; 39 | } 40 | 41 | @Nullable 42 | public EnchantmentInstance getClue() { 43 | Registry registry = level.registryAccess().registryOrThrow(Registries.ENCHANTMENT); 44 | var ext = EnchantmentMenuExtension.morejs$cast(menu); 45 | var id = ext.morejs$getEnchantmentClues()[dataSlotIndex]; 46 | var level = ext.morejs$getLevelClues()[dataSlotIndex]; 47 | return registry.getHolder(id).map(enchantment -> new EnchantmentInstance(enchantment, level)).orElse(null); 48 | } 49 | 50 | public void setClue(Holder enchantment, int enchantmentLevel) { 51 | Registry registry = level.registryAccess().registryOrThrow(Registries.ENCHANTMENT); 52 | var ext = EnchantmentMenuExtension.morejs$cast(menu); 53 | ext.morejs$getEnchantmentClues()[dataSlotIndex] = registry.getId(enchantment.value()); 54 | ext.morejs$getLevelClues()[dataSlotIndex] = enchantmentLevel; 55 | } 56 | 57 | public void setClue(EnchantmentInstance ei) { 58 | setClue(ei.enchantment, ei.level); 59 | } 60 | 61 | public void randomClue() { 62 | if (enchantments.isEmpty()) { 63 | clearClue(); 64 | return; 65 | } 66 | 67 | var ext = EnchantmentMenuExtension.morejs$cast(menu); 68 | EnchantmentInstance ei = enchantments.get(ext.morejs$getRandom().nextInt(enchantments.size())); 69 | setClue(ei); 70 | } 71 | 72 | public void clearClue() { 73 | var ext = EnchantmentMenuExtension.morejs$cast(menu); 74 | ext.morejs$getEnchantmentClues()[dataSlotIndex] = -1; 75 | ext.morejs$getLevelClues()[dataSlotIndex] = 0; 76 | } 77 | 78 | public List getEnchantments() { 79 | return enchantments; 80 | } 81 | 82 | public List getEnchantmentIds() { 83 | return getEnchantments() 84 | .stream() 85 | .flatMap(e -> e.enchantment.unwrapKey().stream()) 86 | .map(ResourceKey::location) 87 | .toList(); 88 | } 89 | 90 | public boolean hasEnchantment(ResourceLocation id) { 91 | return hasEnchantment(id, IntRange.all()); 92 | } 93 | 94 | public boolean hasEnchantment(ResourceLocation id, IntRange range) { 95 | Registry registry = level.registryAccess().registryOrThrow(Registries.ENCHANTMENT); 96 | return registry.getHolder(id).filter(ref -> { 97 | for (EnchantmentInstance ei : getEnchantments()) { 98 | if (ei.enchantment == ref && range.test(ei.level)) return true; 99 | } 100 | 101 | return false; 102 | }).isPresent(); 103 | } 104 | 105 | public void removeEnchantments(BiPredicate, Integer> consumer) { 106 | getEnchantments().removeIf(i -> consumer.test(i.enchantment, i.level)); 107 | } 108 | 109 | public void addEnchantment(Holder enchantment, int level) { 110 | Objects.requireNonNull(enchantment, "Enchantment does not exist"); 111 | getEnchantments().add(new EnchantmentInstance(enchantment, level)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/MerchantOfferMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.features.villager.OfferExtension; 4 | import net.minecraft.core.component.DataComponentPredicate; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.item.trading.ItemCost; 8 | import net.minecraft.world.item.trading.MerchantOffer; 9 | import org.spongepowered.asm.mixin.*; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | import java.util.Optional; 16 | 17 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 18 | @Mixin(MerchantOffer.class) 19 | public class MerchantOfferMixin implements OfferExtension { 20 | 21 | @Mutable @Shadow @Final private ItemCost baseCostA; 22 | @Mutable @Shadow @Final private Optional costB; 23 | @Mutable @Shadow @Final private ItemStack result; 24 | @Mutable @Shadow @Final private int maxUses; 25 | @Mutable @Shadow @Final private boolean rewardExp; 26 | @Shadow private int demand; 27 | @Mutable @Shadow @Final private int xp; 28 | @Mutable @Shadow @Final private float priceMultiplier; 29 | @Unique private boolean morejs$isDisabled; 30 | 31 | @Override 32 | public MerchantOffer morejs$self() { 33 | return (MerchantOffer) (Object) this; 34 | } 35 | 36 | @Override 37 | public boolean morejs$isDisabled() { 38 | return this.morejs$isDisabled; 39 | } 40 | 41 | @Override 42 | public void morejs$setDisabled(boolean disabled) { 43 | this.morejs$isDisabled = disabled; 44 | } 45 | 46 | @Override 47 | public ItemStack morejs$getFirstCost() { 48 | return this.baseCostA.itemStack(); 49 | } 50 | 51 | @Override 52 | public void morejs$setFirstCost(ItemStack itemStack) { 53 | ItemStack copy = itemStack.copy(); 54 | this.baseCostA = new ItemCost( 55 | copy.getItem().builtInRegistryHolder(), 56 | copy.getCount(), 57 | DataComponentPredicate.allOf(copy.getComponents()), 58 | copy 59 | ); 60 | } 61 | 62 | @Override 63 | public ItemStack morejs$getSecondCost() { 64 | return this.costB.map(ItemCost::itemStack).orElse(ItemStack.EMPTY); 65 | } 66 | 67 | @Override 68 | public void morejs$setSecondCost(ItemStack itemStack) { 69 | ItemStack copy = itemStack.copy(); 70 | var cost = new ItemCost( 71 | copy.getItem().builtInRegistryHolder(), 72 | copy.getCount(), 73 | DataComponentPredicate.allOf(copy.getComponents()), 74 | copy 75 | ); 76 | 77 | this.costB = Optional.of(cost); 78 | } 79 | 80 | @Override 81 | public ItemStack morejs$getOutput() { 82 | return this.result; 83 | } 84 | 85 | @Override 86 | public void morejs$setOutput(ItemStack itemStack) { 87 | this.result = itemStack; 88 | } 89 | 90 | @Override 91 | public void morejs$setMaxUses(int maxUses) { 92 | this.maxUses = maxUses; 93 | } 94 | 95 | @Override 96 | public void morejs$setDemand(int demand) { 97 | this.demand = demand; 98 | } 99 | 100 | @Override 101 | public void morejs$setVillagerExperience(int villagerExperience) { 102 | this.xp = villagerExperience; 103 | } 104 | 105 | @Override 106 | public void morejs$setPriceMultiplier(float priceMultiplier) { 107 | this.priceMultiplier = priceMultiplier; 108 | } 109 | 110 | @Override 111 | public void morejs$setRewardExp(boolean rewardExp) { 112 | this.rewardExp = rewardExp; 113 | } 114 | 115 | @Override 116 | public boolean morejs$isRewardingExp() { 117 | return this.rewardExp; 118 | } 119 | 120 | @Inject(method = "isOutOfStock", at = @At("HEAD"), cancellable = true) 121 | private void morejs$cancelIfDisabled(CallbackInfoReturnable cir) { 122 | if (this.morejs$isDisabled) { 123 | cir.setReturnValue(true); 124 | } 125 | } 126 | 127 | @Inject(method = "writeToStream", at = @At("RETURN")) 128 | private static void morejs$injectWriteToStream(RegistryFriendlyByteBuf buf, MerchantOffer offer, CallbackInfo ci) { 129 | 130 | buf.writeBoolean(((OfferExtension) offer).morejs$isDisabled()); 131 | } 132 | 133 | @Inject(method = "createFromStream", at = @At("RETURN")) 134 | private static void morejs$injectcreateFromStream(RegistryFriendlyByteBuf buf, CallbackInfoReturnable cir) { 135 | boolean isDisabled = buf.readBoolean(); 136 | MerchantOffer offer = cir.getReturnValue(); 137 | ((OfferExtension) offer).morejs$setDisabled(isDisabled); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/events/WandererTradingEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.events; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeFilter; 4 | import com.almostreliable.morejs.features.villager.TradeItem; 5 | import com.almostreliable.morejs.features.villager.TradeMatcher; 6 | import com.almostreliable.morejs.features.villager.VillagerUtils; 7 | import com.almostreliable.morejs.features.villager.trades.CustomTrade; 8 | import com.almostreliable.morejs.features.villager.trades.SimpleTrade; 9 | import com.almostreliable.morejs.features.villager.trades.TransformableTrade; 10 | import com.google.common.base.Preconditions; 11 | import dev.latvian.mods.kubejs.event.KubeEvent; 12 | import dev.latvian.mods.kubejs.script.ConsoleJS; 13 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 14 | import net.minecraft.world.entity.npc.VillagerTrades; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | public class WandererTradingEventJS implements KubeEvent { 22 | private final Int2ObjectMap> trades; 23 | 24 | public WandererTradingEventJS(Int2ObjectMap> trades) { 25 | this.trades = trades; 26 | } 27 | 28 | public List getTrades(int level) { 29 | checkLevel(level); 30 | return trades.computeIfAbsent(level, $ -> new ArrayList<>()); 31 | } 32 | 33 | public SimpleTrade addTrade(int level, TradeItem[] inputs, TradeItem output) { 34 | Preconditions.checkArgument(!output.isEmpty(), "Sell item cannot be empty"); 35 | Preconditions.checkArgument(inputs.length != 0, "Buyer items cannot be empty"); 36 | Preconditions.checkArgument(Arrays.stream(inputs).noneMatch(TradeItem::isEmpty), "Buyer items cannot be empty"); 37 | 38 | SimpleTrade trade = VillagerUtils.createSimpleTrade(inputs, output); 39 | return addTrade(level, trade); 40 | } 41 | 42 | public T addTrade(int level, T trade) { 43 | Objects.requireNonNull(trade); 44 | getTrades(level).add(trade); 45 | return trade; 46 | } 47 | 48 | public void addCustomTrade(int level, TransformableTrade.Transformer transformer) { 49 | getTrades(level).add(new CustomTrade(transformer)); 50 | } 51 | 52 | public void removeTrades(TradeFilter filter) { 53 | trades.forEach((level, listings) -> { 54 | var matcher = new TradeMatcher(filter, (first, second, output) -> ConsoleJS.SERVER.info( 55 | "Removing wanderer trade for level " + level + ": " + first + " & " + second + " -> " + output)); 56 | 57 | if (matcher.matchMerchantLevel(level)) { 58 | listings.removeIf(itemListing -> { 59 | if (itemListing instanceof TradeMatcher.Filterable filterable) { 60 | return filterable.matchesTradeFilter(matcher); 61 | } 62 | return false; 63 | }); 64 | } 65 | }); 66 | } 67 | 68 | public void removeVanillaTypedTrades() { 69 | getTrades(1).removeIf(VillagerUtils::isVanillaTypedTrade); 70 | getTrades(2).removeIf(VillagerUtils::isVanillaTypedTrade); 71 | } 72 | 73 | @Deprecated(forRemoval = true) 74 | public void removeVanillaTrades() { 75 | ConsoleJS.SERVER.error("removeVanillaTrades is deprecated, use removeVanillaTypedTrades instead"); 76 | removeVanillaTypedTrades(); 77 | } 78 | 79 | public void removeVanillaTypedTrades(int level) { 80 | checkLevel(level); 81 | getTrades(level).removeIf(VillagerUtils::isVanillaTypedTrade); 82 | } 83 | 84 | @Deprecated(forRemoval = true) 85 | public void removeVanillaTrades(int level) { 86 | ConsoleJS.SERVER.error("removeVanillaTrades is deprecated, use removeVanillaTypedTrades instead"); 87 | removeVanillaTypedTrades(level); 88 | } 89 | 90 | public void removeModdedTypedTrades() { 91 | getTrades(1).removeIf(VillagerUtils::isModdedTypedTrade); 92 | getTrades(2).removeIf(VillagerUtils::isModdedTypedTrade); 93 | } 94 | 95 | @Deprecated(forRemoval = true) 96 | public void removeModdedTrades() { 97 | ConsoleJS.SERVER.error("removeModdedTrades is deprecated, use removeModdedTypedTrades instead"); 98 | removeModdedTypedTrades(); 99 | } 100 | 101 | public void removeModdedTypedTrades(int level) { 102 | checkLevel(level); 103 | getTrades(level).removeIf(VillagerUtils::isModdedTypedTrade); 104 | } 105 | 106 | @Deprecated(forRemoval = true) 107 | public void removeModdedTrades(int level) { 108 | ConsoleJS.SERVER.error("removeModdedTrades is deprecated, use removeModdedTypedTrades instead"); 109 | removeModdedTypedTrades(level); 110 | } 111 | 112 | private void checkLevel(int level) { 113 | Preconditions.checkArgument(1 <= level && level <= 2, "Level must be between 1 and 2"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/structure/StructureAfterPlaceEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.structure; 2 | 3 | import dev.latvian.mods.kubejs.level.KubeLevelEvent; 4 | import net.minecraft.core.registries.BuiltInRegistries; 5 | import net.minecraft.core.registries.Registries; 6 | import net.minecraft.resources.ResourceLocation; 7 | import net.minecraft.server.level.ServerLevel; 8 | import net.minecraft.util.RandomSource; 9 | import net.minecraft.world.level.ChunkPos; 10 | import net.minecraft.world.level.StructureManager; 11 | import net.minecraft.world.level.WorldGenLevel; 12 | import net.minecraft.world.level.chunk.ChunkGenerator; 13 | import net.minecraft.world.level.levelgen.structure.BoundingBox; 14 | import net.minecraft.world.level.levelgen.structure.Structure; 15 | import net.minecraft.world.level.levelgen.structure.StructurePiece; 16 | import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer; 17 | import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType; 18 | import net.minecraft.world.phys.AABB; 19 | 20 | import java.util.Collection; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | 25 | public class StructureAfterPlaceEventJS implements KubeLevelEvent { 26 | 27 | private final Structure structure; 28 | private final WorldGenLevel worldGenLevel; 29 | private final StructureManager structureManager; 30 | private final ChunkGenerator chunkGenerator; 31 | private final RandomSource randomSource; 32 | private final BoundingBox boundingBox; 33 | private final ChunkPos chunkPos; 34 | private final PiecesContainer piecesContainer; 35 | private Map intersectionMap; 36 | 37 | public StructureAfterPlaceEventJS(Structure structure, WorldGenLevel worldGenLevel, StructureManager structureManager, ChunkGenerator chunkGenerator, RandomSource randomSource, BoundingBox boundingBox, ChunkPos chunkPos, PiecesContainer piecesContainer) { 38 | this.structure = structure; 39 | this.worldGenLevel = worldGenLevel; 40 | this.structureManager = structureManager; 41 | this.chunkGenerator = chunkGenerator; 42 | this.randomSource = randomSource; 43 | this.boundingBox = boundingBox; 44 | this.chunkPos = chunkPos; 45 | this.piecesContainer = piecesContainer; 46 | } 47 | 48 | public Structure getStructure() { 49 | return structure; 50 | } 51 | 52 | public StructureManager getStructureManager() { 53 | return structureManager; 54 | } 55 | 56 | public ChunkGenerator getChunkGenerator() { 57 | return chunkGenerator; 58 | } 59 | 60 | public RandomSource getRandomSource() { 61 | return randomSource; 62 | } 63 | 64 | public BoundingBox getChunkBoundingBox() { 65 | return boundingBox; 66 | } 67 | 68 | public ChunkPos getChunkPos() { 69 | return chunkPos; 70 | } 71 | 72 | public PiecesContainer getPiecesContainer() { 73 | return piecesContainer; 74 | } 75 | 76 | public BoundingBox getStructureBoundingBox() { 77 | return piecesContainer.calculateBoundingBox(); 78 | } 79 | 80 | public ServerLevel getLevel() { 81 | return worldGenLevel.getLevel(); 82 | } 83 | 84 | public WorldGenLevel getWorldGenLevel() { 85 | return worldGenLevel; 86 | } 87 | 88 | public ResourceLocation getPieceType(StructurePieceType pieceType) { 89 | return Objects.requireNonNull(BuiltInRegistries.STRUCTURE_PIECE.getKey(pieceType)); 90 | } 91 | 92 | public ResourceLocation getId() { 93 | return Objects.requireNonNull(structureManager 94 | .registryAccess() 95 | .registryOrThrow(Registries.STRUCTURE) 96 | .getKey(structure)); 97 | } 98 | 99 | public String getGenStep() { 100 | return structure.step().getName(); 101 | } 102 | 103 | public Collection getIntersectionBoxes() { 104 | return getIntersectionMap().values(); 105 | } 106 | 107 | public Collection getIntersectionPieces() { 108 | return getIntersectionMap().keySet(); 109 | } 110 | 111 | public Map getIntersectionMap() { 112 | if (intersectionMap == null) { 113 | Map map = new HashMap<>(); 114 | for (StructurePiece sp : piecesContainer.pieces()) { 115 | if (boundingBox.intersects(sp.getBoundingBox())) { 116 | AABB aabb = AABB.of(boundingBox).intersect(AABB.of(sp.getBoundingBox())); 117 | BoundingBox bb = new BoundingBox((int) aabb.minX, 118 | (int) aabb.minY, 119 | (int) aabb.minZ, 120 | (int) aabb.maxX - 1, 121 | (int) aabb.maxY - 1, 122 | (int) aabb.maxZ - 1); 123 | map.put(sp, bb); 124 | } 125 | } 126 | 127 | intersectionMap = map; 128 | } 129 | 130 | return intersectionMap; 131 | } 132 | 133 | public ResourceLocation getType() { 134 | return Objects.requireNonNull(BuiltInRegistries.STRUCTURE_TYPE.getKey(structure.type())); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/mixin/villager/VillagerTradesMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.mixin.villager; 2 | 3 | import com.almostreliable.morejs.features.villager.TradeMatcher; 4 | import com.almostreliable.morejs.features.villager.TradeTypes; 5 | import net.minecraft.world.entity.npc.VillagerTrades; 6 | import net.minecraft.world.item.Item; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.item.Items; 9 | import net.minecraft.world.item.trading.ItemCost; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | 14 | public class VillagerTradesMixin { 15 | 16 | @Mixin(VillagerTrades.EmeraldForItems.class) 17 | private static class EmeraldForItemsMixin implements TradeMatcher.Filterable { 18 | @Shadow @Final private ItemCost itemStack; 19 | 20 | @Override 21 | public boolean matchesTradeFilter(TradeMatcher filter) { 22 | return filter.match(itemStack, new ItemStack(Items.EMERALD), 23 | TradeTypes.EmeraldForItems); 24 | } 25 | } 26 | 27 | @Mixin(VillagerTrades.ItemsForEmeralds.class) 28 | private static class ItemsForEmeraldsMixin implements TradeMatcher.Filterable { 29 | @Shadow @Final private int emeraldCost; 30 | 31 | @Shadow @Final private ItemStack itemStack; 32 | 33 | @Override 34 | public boolean matchesTradeFilter(TradeMatcher filter) { 35 | return filter.match(new ItemStack(Items.EMERALD, this.emeraldCost), 36 | this.itemStack, 37 | TradeTypes.ItemsForEmeralds); 38 | } 39 | } 40 | 41 | @Mixin(VillagerTrades.ItemsAndEmeraldsToItems.class) 42 | private static class ItemsAndEmeraldsToItemsMixin implements TradeMatcher.Filterable { 43 | 44 | @Shadow @Final private int emeraldCost; 45 | 46 | @Shadow @Final private ItemCost fromItem; 47 | 48 | @Shadow @Final private ItemStack toItem; 49 | 50 | @Override 51 | public boolean matchesTradeFilter(TradeMatcher filter) { 52 | return filter.match(new ItemStack(Items.EMERALD, this.emeraldCost), 53 | this.fromItem, 54 | this.toItem, 55 | TradeTypes.ItemsAndEmeraldsToItems); 56 | } 57 | } 58 | 59 | @Mixin(VillagerTrades.SuspiciousStewForEmerald.class) 60 | private static class SuspiciousStewForEmeraldMixin implements TradeMatcher.Filterable { 61 | @Override 62 | public boolean matchesTradeFilter(TradeMatcher filter) { 63 | return filter.match(new ItemStack(Items.EMERALD), new ItemStack(Items.SUSPICIOUS_STEW), 64 | TradeTypes.SuspiciousStewForEmeralds); 65 | } 66 | } 67 | 68 | @Mixin(VillagerTrades.EnchantedItemForEmeralds.class) 69 | private static class EnchantedItemForEmeraldsMixin implements TradeMatcher.Filterable { 70 | @Shadow @Final private ItemStack itemStack; 71 | 72 | @Override 73 | public boolean matchesTradeFilter(TradeMatcher filter) { 74 | return filter.match(new ItemStack(Items.EMERALD, 64), this.itemStack, TradeTypes.EnchantedItemForEmeralds); 75 | } 76 | } 77 | 78 | @Mixin(VillagerTrades.EmeraldsForVillagerTypeItem.class) 79 | private static class EmeraldsForVillagerTypeItemMixin implements TradeMatcher.Filterable { 80 | @Override 81 | public boolean matchesTradeFilter(TradeMatcher filter) { 82 | return filter.match(new ItemStack(Items.EMERALD), 83 | new ItemStack(Items.BARRIER), 84 | TradeTypes.EmeraldsForVillagerTypeItem); 85 | } 86 | } 87 | 88 | @Mixin(VillagerTrades.TippedArrowForItemsAndEmeralds.class) 89 | private static class TippedArrowForItemsAndEmeraldsMixin implements TradeMatcher.Filterable { 90 | @Shadow @Final private int emeraldCost; 91 | 92 | @Shadow @Final private Item fromItem; 93 | 94 | @Shadow @Final private int fromCount; 95 | 96 | @Shadow @Final private ItemStack toItem; 97 | 98 | @Shadow @Final private int toCount; 99 | 100 | @Override 101 | public boolean matchesTradeFilter(TradeMatcher filter) { 102 | return filter.match(new ItemStack(Items.EMERALD, this.emeraldCost), 103 | new ItemStack(this.fromItem, this.fromCount), 104 | new ItemStack(this.toItem.getItem(), this.toCount), 105 | TradeTypes.TippedArrowForItemsAndEmeralds); 106 | } 107 | } 108 | 109 | @Mixin(VillagerTrades.EnchantBookForEmeralds.class) 110 | private static class EnchantBookForEmeraldsMixin implements TradeMatcher.Filterable { 111 | 112 | @Override 113 | public boolean matchesTradeFilter(TradeMatcher filter) { 114 | return filter.match(new ItemStack(Items.EMERALD, 64), new ItemStack(Items.ENCHANTED_BOOK), 115 | TradeTypes.EnchantBookForEmeralds); 116 | } 117 | } 118 | 119 | @Mixin(VillagerTrades.TreasureMapForEmeralds.class) 120 | private static class TreasureMapForEmeraldsMixin implements TradeMatcher.Filterable { 121 | @Override 122 | public boolean matchesTradeFilter(TradeMatcher filter) { 123 | return filter.match(new ItemStack(Items.EMERALD, 64), new ItemStack(Items.FILLED_MAP), 124 | TradeTypes.TreasureMapForEmeralds); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | - / 5 | 6 | ## [0.14.1] - 2024-11-08 7 | 8 | - Rename methods in wanderer trading event to match villager trading event 9 | 10 | ## [0.14.0] - 2024-10-31 11 | 12 | - Added `MoreJS.postUpdateOffers` event which triggers after a Villager updates their offers 13 | 14 | ## [0.13.1] - 2024-09-13 15 | 16 | - Fix removing of potion brewing 17 | - Fix enchantments not being applied to the item in the enchantment table 18 | 19 | ## [0.13.0] - 2024-07-26 20 | 21 | - Rename some functions in VillagerUtils to fit other functions. 22 | - Fix trade cache does not get cleared after reload. 23 | 24 | ## [0.12.0] - 2024-07-26 25 | 26 | - Refactor potion brewing event to use a filter object for removing brewing recipes 27 | 28 | ## [0.11.0] - 2024-07-26 29 | 30 | - Update to 1.21 31 | 32 | ## [0.10.0] - 2024-05-01 33 | 34 | - Add `setRewardExp` and `isRewardingExp` for offers 35 | 36 | ## [0.9.0] - 2024-03-22 37 | 38 | - Add `.replaceItems(ingredient, item)` when working with offers similar to `.replaceEmeralds` 39 | - Fix a bug that rare trades from wanderer are not detected on update events 40 | 41 | ## [0.8.0] - 2024-03-22 42 | 43 | - Add `event.clickedButton`, `event.costs`, `event.enchantments`, `event.enchantmentIds` 44 | to `MoreJSEvents.enchantmentTableEnchant` event 45 | 46 | ## [0.7.0] - 2024-03-14 47 | - Add `removeContainer` and `addContainerRecipe` to potion event 48 | 49 | ## [0.6.0] - 2024-02-04 50 | - Fix [#9](https://github.com/AlmostReliable/morejs/issues/9) 51 | - Add utility to `VillagerUtils` 52 | - `.setAbstractTrades(tradeMap, level, trades)` 53 | - `.getAbstractTrades(tradeMap, level)` 54 | - `.getVillagerTrades(profession)` 55 | - `.getVillagerTrades(profession, level)` 56 | - `.getRandomVillagerTrade(profession)` 57 | - `.getRandomVillagerTrade(profession, level)` 58 | - `.getWandererTrades(level)` 59 | - `.getRandomWandererTrade(level)` 60 | 61 | ## [0.5.0] - 2023-10-21 62 | - Fix [#8](https://github.com/AlmostReliable/morejs/issues/8) 63 | 64 | ## [0.4.0] - 2023-10-21 65 | - Add `structureAfterPlace` event. Thanks to [Pietro Lopes](https://github.com/pietro-lopes) 66 | 67 | ## [0.3.0] - 2023-09-10 68 | - Update to KubeJS 6.3 1.20.1 69 | 70 | ## [0.2.0] - 2023-07-18 71 | - Update to KubeJS 6.1 72 | 73 | ## [0.1.1] - 2023-05-26 74 | - Add `TradeItem` to allow price ranges for trades 75 | - Add custom potion removing for forge 76 | 77 | ## [0.1.0] - 2023-05-16 78 | - Add event `filterEnchantedBookTrade` and `filterAvailableEnchantments` to filter enchantments 79 | 80 | ## [0.0.8] - 2023-05-09 81 | - Add event `piglinPlayerBehavior` for piglin behavior (like wearing gold) 82 | - Add events `updateAbstractVillagerOffers`, `updateVillagerOffers` and `updateWandererOffers` to handle villager trading when it's updated 83 | - Add basic villager trade from forge to filters 84 | 85 | ## [0.0.7] - 2023-01-19 86 | - Change mixin priority for enchantment feature 87 | 88 | ## [0.0.6] - 2023-01-08 89 | - Fix loading issue for villager trades on Forge 90 | - Fix missing accesswidener in fabric.mod.json 91 | 92 | ## [0.0.5] - 2022-11-24 93 | - Add `registerPotionBrewing` event 94 | 95 | ## [0.0.4] - 2022-11-24 96 | - Add `enchantmentTableIsEnchantable` event 97 | - Reset the item for `enchantmentTableChanged` 98 | - Add more utilities to events to check enchantment ids directly 99 | 100 | ## [0.0.3] - 2022-10-21 101 | - Fix crash with new KubeJS version 102 | 103 | ## [0.0.2] - 2022-10-21 104 | - Add trade filters 105 | 106 | ## [0.0.1] - 2022-08-30 107 | - Add `morejs.villager.trading` event. 108 | - Add `morejs.wanderer.trading` event. 109 | - Add `morejs.enchantment_table.changed` event. 110 | - Add `morejs.enchantment_table.enchant` event. 111 | - Add `morejs.enchantment_table.tooltip` event. 112 | - Add `morejs.teleport` event. 113 | - Add `morejs.structure.load` event. 114 | - Add `morejs.player.xp_change` event. 115 | - Add `morejs.player.start_trading` event. 116 | 117 | 118 | [0.14.1]: https://github.com/AlmostReliable/morejs/releases/tag/v1.21-neoforge-0.14.1 119 | [0.14.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.21-neoforge-0.14.0 120 | [0.13.1]: https://github.com/AlmostReliable/morejs/releases/tag/v1.21-neoforge-0.13.1 121 | [0.13.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.21-neoforge-0.13.0 122 | [0.12.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.21-neoforge-0.12.0 123 | [0.11.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.21-neoforge-0.11.0 124 | [0.10.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.10.0 125 | [0.9.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.9.0 126 | [0.8.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.8.0 127 | [0.7.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.7.0 128 | [0.6.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.6.0 129 | [0.5.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.5.0 130 | [0.4.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.4.0 131 | [0.3.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.20.1-0.3.0 132 | [0.2.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.2.0-beta 133 | [0.1.1]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.1.1-beta 134 | [0.1.0]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.1.0-beta 135 | [0.0.8]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.8-beta 136 | [0.0.7]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.7-beta 137 | [0.0.6]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.6-beta 138 | [0.0.5]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.5-beta 139 | [0.0.4]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.4-beta 140 | [0.0.3]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.3-beta 141 | [0.0.2]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.2-beta 142 | [0.0.1]: https://github.com/AlmostReliable/morejs/releases/tag/v1.19-0.0.1-beta 143 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/events/VillagerTradingEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager.events; 2 | 3 | import com.almostreliable.morejs.features.villager.*; 4 | import com.almostreliable.morejs.features.villager.trades.CustomTrade; 5 | import com.almostreliable.morejs.features.villager.trades.SimpleTrade; 6 | import com.almostreliable.morejs.features.villager.trades.TransformableTrade; 7 | import com.google.common.base.Preconditions; 8 | import com.google.common.collect.Table; 9 | import dev.latvian.mods.kubejs.event.KubeEvent; 10 | import dev.latvian.mods.kubejs.script.ConsoleJS; 11 | import net.minecraft.core.Holder; 12 | import net.minecraft.core.registries.BuiltInRegistries; 13 | import net.minecraft.world.entity.npc.VillagerProfession; 14 | import net.minecraft.world.entity.npc.VillagerTrades; 15 | 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Objects; 19 | import java.util.Set; 20 | import java.util.function.Consumer; 21 | import java.util.stream.Collectors; 22 | 23 | public class VillagerTradingEventJS implements KubeEvent { 24 | private final Table> trades; 25 | 26 | public VillagerTradingEventJS(Table> allTrades) { 27 | trades = allTrades; 28 | } 29 | 30 | public List getTrades(Holder profession, int level) { 31 | Preconditions.checkArgument(1 <= level && level <= 5, "Level must be between 1 and 5"); 32 | Preconditions.checkArgument(!profession.value().equals(VillagerProfession.NONE), 33 | "No or invalid profession specified"); 34 | return Objects.requireNonNull(trades.get(profession.value(), level)); 35 | } 36 | 37 | public SimpleTrade addTrade(Holder profession, int level, TradeItem[] inputs, TradeItem output) { 38 | Preconditions.checkArgument(!output.isEmpty(), "Sell item cannot be empty"); 39 | Preconditions.checkArgument(inputs.length != 0, "Buyer items cannot be empty"); 40 | Preconditions.checkArgument(Arrays.stream(inputs).noneMatch(TradeItem::isEmpty), "Buyer items cannot be empty"); 41 | 42 | SimpleTrade trade = VillagerUtils.createSimpleTrade(inputs, output); 43 | return addTrade(profession, level, trade); 44 | } 45 | 46 | public T addTrade(Holder profession, int level, T trade) { 47 | Objects.requireNonNull(trade); 48 | getTrades(profession, level).add(trade); 49 | return trade; 50 | } 51 | 52 | public void addCustomTrade(Holder profession, int level, TransformableTrade.Transformer transformer) { 53 | getTrades(profession, level).add(new CustomTrade(transformer)); 54 | } 55 | 56 | public void removeTrades(TradeFilter filter) { 57 | forEachTrades((listings, level, profession) -> { 58 | var matcher = new TradeMatcher(filter, (first, second, output) -> { 59 | String secondStr = second == null || second.isEmpty() ? "" : " & " + second; 60 | 61 | var profId = BuiltInRegistries.VILLAGER_PROFESSION.getKey(profession); 62 | ConsoleJS.SERVER.info( 63 | "Removing villager trade for profession '" + profId + "' for level " + level + ": " + first + 64 | secondStr + " -> " + output); 65 | }); 66 | 67 | if (matcher.matchProfession(profession) && matcher.matchMerchantLevel(level)) { 68 | listings.removeIf(itemListing -> { 69 | if (itemListing instanceof TradeMatcher.Filterable filterable) { 70 | return filterable.matchesTradeFilter(matcher); 71 | } 72 | return false; 73 | }); 74 | } 75 | }); 76 | } 77 | 78 | public void removeVanillaTypedTrades() { 79 | forEachTrades((listings, level, profession) -> { 80 | listings.removeIf(VillagerUtils::isVanillaTypedTrade); 81 | }); 82 | } 83 | 84 | public void removeVanillaTypedTrades(List> profession) { 85 | removeVanillaTypedTrades(profession, IntRange.all()); 86 | } 87 | 88 | public void removeVanillaTypedTrades(List> professions, IntRange intRange) { 89 | forEachTrades(professions, intRange, itemListings -> { 90 | itemListings.removeIf(VillagerUtils::isVanillaTypedTrade); 91 | }); 92 | } 93 | 94 | public void removeModdedTypedTrades() { 95 | forEachTrades((listings, level, profession) -> { 96 | listings.removeIf(VillagerUtils::isModdedTypedTrade); 97 | }); 98 | } 99 | 100 | public void removeModdedTypedTrades(List> profession) { 101 | removeModdedTypedTrades(profession, IntRange.all()); 102 | } 103 | 104 | public void removeModdedTypedTrades(List> professions, IntRange intRange) { 105 | forEachTrades(professions, intRange, itemListings -> { 106 | itemListings.removeIf(VillagerUtils::isModdedTypedTrade); 107 | }); 108 | } 109 | 110 | public void forEachTrades(ForEachCallback callback) { 111 | trades.rowMap().forEach((profession, tradesPerLevel) -> { 112 | tradesPerLevel.forEach((level, itemListings) -> { 113 | callback.accept(itemListings, level, profession); 114 | }); 115 | }); 116 | } 117 | 118 | public void forEachTrades(List> professions, IntRange intRange, Consumer> consumer) { 119 | Set filter = professions.stream().map(Holder::value).collect(Collectors.toSet()); 120 | forEachTrades((itemListings, level, profession) -> { 121 | if (filter.contains(profession) && intRange.test(level)) { 122 | consumer.accept(itemListings); 123 | } 124 | }); 125 | } 126 | 127 | public interface ForEachCallback { 128 | void accept(List listings, int level, VillagerProfession profession); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Copyright 2015 the original author or 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 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | ARGV=("$@") 176 | eval set -- $DEFAULT_JVM_OPTS 177 | 178 | IFS=$' 179 | ' read -rd '' -a JAVA_OPTS_ARR <<< "$(echo $JAVA_OPTS | xargs -n1)" 180 | IFS=$' 181 | ' read -rd '' -a GRADLE_OPTS_ARR <<< "$(echo $GRADLE_OPTS | xargs -n1)" 182 | 183 | exec "$JAVACMD" "$@" "${JAVA_OPTS_ARR[@]}" "${GRADLE_OPTS_ARR[@]}" "-Dorg.gradle.appname=$APP_BASE_NAME" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "${ARGV[@]}" 184 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/potion/PotionBrewingRegisterEvent.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.potion; 2 | 3 | import com.almostreliable.morejs.mixin.PotionBrewingBuilderAccessor; 4 | import com.almostreliable.morejs.util.Utils; 5 | import com.google.common.base.Preconditions; 6 | import dev.latvian.mods.kubejs.event.KubeEvent; 7 | import dev.latvian.mods.kubejs.script.ConsoleJS; 8 | import net.minecraft.core.Holder; 9 | import net.minecraft.core.registries.BuiltInRegistries; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraft.world.item.Item; 12 | import net.minecraft.world.item.ItemStack; 13 | import net.minecraft.world.item.Items; 14 | import net.minecraft.world.item.alchemy.Potion; 15 | import net.minecraft.world.item.alchemy.PotionBrewing; 16 | import net.minecraft.world.item.alchemy.Potions; 17 | import net.minecraft.world.item.crafting.Ingredient; 18 | import net.neoforged.neoforge.common.brewing.BrewingRecipe; 19 | import net.neoforged.neoforge.common.brewing.IBrewingRecipe; 20 | import org.apache.commons.lang3.StringUtils; 21 | 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.ListIterator; 25 | 26 | public class PotionBrewingRegisterEvent implements KubeEvent { 27 | 28 | private final PotionBrewing.Builder potionBrewing; 29 | private final PotionBrewingBuilderAccessor potionBrewingAccessor; 30 | 31 | public PotionBrewingRegisterEvent(PotionBrewing.Builder potionBrewing) { 32 | this.potionBrewing = potionBrewing; 33 | this.potionBrewingAccessor = (PotionBrewingBuilderAccessor) potionBrewing; 34 | } 35 | 36 | private static ResourceLocation key(Potion potion) { 37 | return BuiltInRegistries.POTION.getKey(potion); 38 | } 39 | 40 | protected void validate(Ingredient ingredient, Ingredient input, ItemStack output) { 41 | Preconditions.checkArgument(input.getItems().length > 0, "Input must have at least one item"); 42 | Preconditions.checkArgument(ingredient.getItems().length > 0, "Ingredient must have at least one item"); 43 | Preconditions.checkArgument(!output.isEmpty(), "Output must not be empty"); 44 | } 45 | 46 | protected void validateSimple(Ingredient ingredient, Potion input, Potion output) { 47 | Preconditions.checkNotNull(input, "Input potion must not be null"); 48 | Preconditions.checkNotNull(ingredient, "Ingredient must not be null"); 49 | Preconditions.checkNotNull(output, "Output potion must not be null"); 50 | Preconditions.checkArgument(ingredient.getItems().length > 0, "Ingredient must have at least one item"); 51 | } 52 | 53 | public void addCustomBrewing(Ingredient ingredient, Ingredient input, ItemStack output) { 54 | validate(ingredient, input, output); 55 | potionBrewing.addRecipe(input, ingredient, output); 56 | } 57 | 58 | public void addPotionBrewing(Ingredient ingredient, Potion input, Potion output) { 59 | validateSimple(ingredient, input, output); 60 | Holder inputRef = BuiltInRegistries.POTION.wrapAsHolder(input); 61 | Holder outputRef = BuiltInRegistries.POTION.wrapAsHolder(output); 62 | potionBrewingAccessor.morejs$getPotionMixes().add(new PotionBrewing.Mix<>(inputRef, ingredient, outputRef)); 63 | } 64 | 65 | public void addPotionBrewing(Ingredient ingredient, Potion output) { 66 | addPotionBrewing(ingredient, Potions.WATER.value(), output); 67 | } 68 | 69 | public void removePotionBrewing(PotionBrewingFilter filter) { 70 | potionBrewingAccessor.morejs$getPotionMixes().removeIf(mix -> { 71 | if (filter.test(mix)) { 72 | ConsoleJS.STARTUP.info( 73 | "Removed potion brewing recipe: " + 74 | key(mix.from().value()) + " + " + 75 | StringUtils.abbreviate(mix.ingredient().toString(), 64) + " -> " + 76 | key(mix.to().value())); 77 | return true; 78 | } 79 | 80 | return false; 81 | }); 82 | } 83 | 84 | public void removeContainer(Ingredient ingredient) { 85 | HashSet removed = new HashSet<>(); 86 | 87 | var containerIt = potionBrewingAccessor.morejs$getContainers().listIterator(); 88 | while (containerIt.hasNext()) { 89 | Ingredient ac = containerIt.next(); 90 | if (Utils.matchesIngredient(ac, ingredient)) { 91 | containerIt.remove(); 92 | for (ItemStack item : ac.getItems()) { 93 | removed.add(item.getItem()); 94 | } 95 | } 96 | } 97 | 98 | 99 | var mixIt = potionBrewingAccessor.morejs$getContainerMixes().listIterator(); 100 | while (mixIt.hasNext()) { 101 | PotionBrewing.Mix mix = mixIt.next(); 102 | var output = mix.to().value(); 103 | if (ingredient.test(output.getDefaultInstance())) { 104 | mixIt.remove(); 105 | removed.add(output); 106 | } 107 | } 108 | 109 | for (Item item : removed) { 110 | ConsoleJS.STARTUP.info("Removed potion container: " + BuiltInRegistries.ITEM.getKey(item)); 111 | } 112 | } 113 | 114 | public void validateContainer(Ingredient ingredient, Item input, Item output) { 115 | Preconditions.checkArgument(input != null && input != Items.AIR, "Input must not be null or air"); 116 | Preconditions.checkNotNull(ingredient, "Ingredient must not be null"); 117 | Preconditions.checkArgument(ingredient.getItems().length > 0, "Ingredient must have at least one item"); 118 | Preconditions.checkArgument(output != null && output != Items.AIR, "Output must not be null or air"); 119 | } 120 | 121 | public void addContainerRecipe(Ingredient ingredient, Item input, Item output) { 122 | validateContainer(ingredient, input, output); 123 | //noinspection deprecation 124 | var mix = new PotionBrewing.Mix<>(input.builtInRegistryHolder(), ingredient, output.builtInRegistryHolder()); 125 | potionBrewingAccessor.morejs$getContainerMixes().add(mix); 126 | } 127 | 128 | public void removeCustomBrewing(CustomBrewingFilter filter) { 129 | ListIterator it = potionBrewingAccessor.morejs$getRecipes().listIterator(); 130 | while (it.hasNext()) { 131 | IBrewingRecipe recipe = it.next(); 132 | if (!(recipe instanceof BrewingRecipe br)) { // BrewingRecipe is the vanilla one 133 | continue; 134 | } 135 | 136 | if (filter.test(br)) { 137 | String s = String.format("Removing custom brewing recipe: [Input: %s][Ingredient: %s][Output: %s]", 138 | br.getInput(), 139 | br.getIngredient(), 140 | br.getOutput()); 141 | ConsoleJS.STARTUP.info(s); 142 | it.remove(); 143 | } 144 | } 145 | } 146 | 147 | public List getCustomBrewingRecipes() { 148 | return potionBrewingAccessor.morejs$getRecipes(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/morejs/features/villager/VillagerUtils.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.morejs.features.villager; 2 | 3 | import com.almostreliable.morejs.MoreJSBinding; 4 | import com.almostreliable.morejs.features.villager.trades.*; 5 | import com.almostreliable.morejs.util.BlockPosFinder; 6 | import com.google.common.collect.ImmutableList; 7 | import net.minecraft.core.HolderSet; 8 | import net.minecraft.core.registries.BuiltInRegistries; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.tags.EnchantmentTags; 11 | import net.minecraft.world.entity.npc.VillagerProfession; 12 | import net.minecraft.world.entity.npc.VillagerTrades; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraft.world.item.enchantment.Enchantment; 15 | import net.minecraft.world.level.biome.Biome; 16 | import net.minecraft.world.level.levelgen.structure.Structure; 17 | 18 | import java.util.*; 19 | import java.util.concurrent.ThreadLocalRandom; 20 | 21 | public class VillagerUtils { 22 | 23 | public static final Map> CACHED_PROFESSION_TRADES = new HashMap<>(); 24 | public static final Set> VANILLA_TRADE_TYPES = Set.of( 25 | VillagerTrades.DyedArmorForEmeralds.class, 26 | VillagerTrades.EnchantBookForEmeralds.class, 27 | VillagerTrades.EnchantedItemForEmeralds.class, 28 | VillagerTrades.ItemsForEmeralds.class, 29 | VillagerTrades.ItemsAndEmeraldsToItems.class, 30 | VillagerTrades.EmeraldForItems.class, 31 | VillagerTrades.TippedArrowForItemsAndEmeralds.class, 32 | VillagerTrades.SuspiciousStewForEmerald.class, 33 | VillagerTrades.TreasureMapForEmeralds.class 34 | ); 35 | 36 | public static boolean isVanillaTypedTrade(VillagerTrades.ItemListing listing) { 37 | return VANILLA_TRADE_TYPES.contains(listing.getClass()); 38 | } 39 | 40 | public static boolean isModdedTypedTrade(VillagerTrades.ItemListing listing) { 41 | return !isVanillaTypedTrade(listing) && !isCustomTypedTrade(listing); 42 | } 43 | 44 | public static boolean isCustomTypedTrade(VillagerTrades.ItemListing listing) { 45 | return listing instanceof TransformableTrade || listing instanceof CustomTrade; 46 | } 47 | 48 | public static Collection getProfessions() { 49 | return BuiltInRegistries.VILLAGER_PROFESSION 50 | .stream() 51 | .filter(p -> !p.name().equals("none")) 52 | .toList(); 53 | } 54 | 55 | public static VillagerProfession getProfession(ResourceLocation id) { 56 | VillagerProfession villagerProfession = BuiltInRegistries.VILLAGER_PROFESSION.get(id); 57 | if (villagerProfession == VillagerProfession.NONE) { 58 | throw new IllegalStateException("No profession with id " + id); 59 | } 60 | 61 | return villagerProfession; 62 | } 63 | 64 | public static SimpleTrade createSimpleTrade(TradeItem[] inputs, TradeItem output) { 65 | return new SimpleTrade(inputs, output); 66 | } 67 | 68 | public static CustomTrade createCustomTrade(TransformableTrade.Transformer transformer) { 69 | return new CustomTrade(transformer); 70 | } 71 | 72 | public static TreasureMapTrade createStructureMapTrade(TradeItem[] inputs, HolderSet structures) { 73 | return new TreasureMapTrade(inputs, 74 | (level, entity) -> MoreJSBinding.findStructure(entity.blockPosition(), level, structures, 100)); 75 | } 76 | 77 | public static TreasureMapTrade createBiomeMapTrade(TradeItem[] inputs, HolderSet biomes) { 78 | return new TreasureMapTrade(inputs, 79 | (level, entity) -> MoreJSBinding.findBiome(entity.blockPosition(), level, biomes, 250)); 80 | } 81 | 82 | public static TreasureMapTrade createCustomMapTrade(TradeItem[] inputs, BlockPosFinder func) { 83 | return new TreasureMapTrade(inputs, func); 84 | } 85 | 86 | public static EnchantedItemTrade createEnchantedItemTrade(TradeItem[] inputs, ItemStack output) { 87 | return new EnchantedItemTrade(inputs, output, EnchantmentTags.ON_TRADED_EQUIPMENT); 88 | } 89 | 90 | public static EnchantedItemTrade createEnchantedItemTrade(TradeItem[] inputs, ItemStack output, HolderSet enchantments) { 91 | return new EnchantedItemTrade(inputs, output, enchantments); 92 | } 93 | 94 | public static StewTrade createStewTrade(TradeItem[] inputs) { 95 | return new StewTrade(inputs); 96 | } 97 | 98 | public static PotionTrade createPotionTrade(TradeItem[] inputs) { 99 | return new PotionTrade(inputs); 100 | } 101 | 102 | public static void setAbstractTrades(Map tradeMap, int level, List listings) { 103 | tradeMap.put(level, listings.toArray(new VillagerTrades.ItemListing[0])); 104 | } 105 | 106 | public static List getAbstractTrades(Map tradeMap, int level) { 107 | var listings = tradeMap.get(level); 108 | if (listings == null) { 109 | return new ArrayList<>(); 110 | } 111 | 112 | return new ArrayList<>(Arrays.asList(listings)); 113 | } 114 | 115 | public static List getVillagerTrades(VillagerProfession profession) { 116 | return CACHED_PROFESSION_TRADES.computeIfAbsent(profession, p -> { 117 | var levelListings = VillagerTrades.TRADES.get(p); 118 | if (levelListings == null) { 119 | return List.of(); 120 | } 121 | 122 | ImmutableList.Builder builder = ImmutableList.builder(); 123 | for (var listings : levelListings.values()) { 124 | for (var listing : listings) { 125 | builder.add(listing); 126 | } 127 | } 128 | 129 | return builder.build(); 130 | }); 131 | } 132 | 133 | public static List getVillagerTrades(VillagerProfession profession, int level) { 134 | var levelListings = VillagerTrades.TRADES.get(profession); 135 | if (levelListings == null) { 136 | return List.of(); 137 | } 138 | 139 | var listings = levelListings.get(level); 140 | if (listings == null) { 141 | return List.of(); 142 | } 143 | 144 | return Arrays.asList(listings); 145 | } 146 | 147 | public static VillagerTrades.ItemListing getRandomVillagerTrade(VillagerProfession profession) { 148 | var trades = getVillagerTrades(profession); 149 | if (trades.isEmpty()) { 150 | throw new IllegalStateException("Profession " + profession + " has no trades"); 151 | } 152 | 153 | return trades.get(ThreadLocalRandom.current().nextInt(trades.size())); 154 | } 155 | 156 | public static VillagerTrades.ItemListing getRandomVillagerTrade(VillagerProfession profession, int level) { 157 | var trades = getVillagerTrades(profession, level); 158 | if (trades.isEmpty()) { 159 | throw new IllegalStateException("Profession " + profession + " on level " + level + " has no trades"); 160 | } 161 | 162 | return trades.get(ThreadLocalRandom.current().nextInt(trades.size())); 163 | } 164 | 165 | public static List getWandererTrades(int level) { 166 | var listings = VillagerTrades.WANDERING_TRADER_TRADES.get(level); 167 | if (listings == null) { 168 | return List.of(); 169 | } 170 | 171 | return Arrays.asList(listings); 172 | } 173 | 174 | public static VillagerTrades.ItemListing getRandomWandererTrade(int level) { 175 | var trades = getWandererTrades(level); 176 | if (trades.isEmpty()) { 177 | throw new IllegalStateException("Wanderer on level " + level + " has no trades"); 178 | } 179 | 180 | return trades.get(ThreadLocalRandom.current().nextInt(trades.size())); 181 | } 182 | } 183 | --------------------------------------------------------------------------------