├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── example_scripts ├── create_loot_table.js ├── custom_condition.js ├── holder_sets.js ├── item_filters.js ├── loot_table_filter.js ├── predicates.js └── print.js ├── examples └── server_scripts │ ├── block │ └── player_equipment.js │ ├── disable_loot_modification.js │ ├── entity │ ├── creeper_additional_gunpowder.js │ ├── double_loot_when_raining.js │ └── entity_mount.js │ ├── functions.js │ ├── item_filters.js │ ├── playerAction.js │ └── some_conditions.js ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── java │ └── com │ │ └── almostreliable │ │ └── lootjs │ │ ├── LookupProvider.java │ │ ├── LootEvents.java │ │ ├── LootJS.java │ │ ├── LootJSConditions.java │ │ ├── LootModificationsAPI.java │ │ ├── PlatformLoader.java │ │ ├── core │ │ ├── LootBucket.java │ │ ├── LootConditionTypes.java │ │ ├── LootContextInfo.java │ │ ├── LootType.java │ │ ├── entry │ │ │ ├── AbstractSimpleLootEntry.java │ │ │ ├── CompositeLootEntry.java │ │ │ ├── DynamicLootEntry.java │ │ │ ├── EmptyLootEntry.java │ │ │ ├── ItemLootEntry.java │ │ │ ├── LootEntry.java │ │ │ ├── SimpleLootEntry.java │ │ │ ├── SingleLootEntry.java │ │ │ ├── TableReferenceLootEntry.java │ │ │ ├── TagLootEntry.java │ │ │ └── package-info.java │ │ ├── filters │ │ │ ├── IdFilter.java │ │ │ ├── ItemFilter.java │ │ │ ├── ItemFilterImpl.java │ │ │ ├── ItemFilterWrapper.java │ │ │ ├── LootTableFilter.java │ │ │ └── package-info.java │ │ └── package-info.java │ │ ├── kube │ │ ├── KubeOps.java │ │ ├── LootJSEvent.java │ │ ├── LootJSPlugin.java │ │ ├── LootModificationEventJS.java │ │ ├── LootTableEventJS.java │ │ ├── package-info.java │ │ └── wrappers │ │ │ ├── BasicWrapper.java │ │ │ ├── ItemFilterWrapper.java │ │ │ ├── ItemPredicateWrapper.java │ │ │ ├── LootEntryWrapper.java │ │ │ ├── MinMaxBoundsWrapper.java │ │ │ ├── MobEffectsPredicateWrapper.java │ │ │ ├── NumberProviderWrapper.java │ │ │ ├── StatePropsPredicateWrapper.java │ │ │ └── package-info.java │ │ ├── loot │ │ ├── AddAttributesFunction.java │ │ ├── LootActionContainer.java │ │ ├── LootCondition.java │ │ ├── LootConditionList.java │ │ ├── LootConditionsContainer.java │ │ ├── LootEntryList.java │ │ ├── LootFunction.java │ │ ├── LootFunctionList.java │ │ ├── LootFunctionsContainer.java │ │ ├── LootModificationEvent.java │ │ ├── LootTableEvent.java │ │ ├── Predicates.java │ │ ├── condition │ │ │ ├── CustomParamPredicate.java │ │ │ ├── IsLightLevel.java │ │ │ ├── MatchAnyInventorySlot.java │ │ │ ├── MatchBiome.java │ │ │ ├── MatchDimension.java │ │ │ ├── MatchEquipmentSlot.java │ │ │ ├── MatchKillerDistance.java │ │ │ ├── MatchPlayer.java │ │ │ ├── MatchStructure.java │ │ │ ├── PlayerParamPredicate.java │ │ │ ├── builder │ │ │ │ ├── DistancePredicateBuilder.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ ├── extension │ │ │ ├── CompositeEntryBaseExtension.java │ │ │ ├── LootContextExtension.java │ │ │ ├── LootItemFunctionExtension.java │ │ │ ├── LootParamsExtension.java │ │ │ ├── LootPoolExtension.java │ │ │ ├── LootTableExtension.java │ │ │ └── package-info.java │ │ ├── modifier │ │ │ ├── GroupedLootAction.java │ │ │ ├── LootAction.java │ │ │ ├── LootModifier.java │ │ │ ├── handler │ │ │ │ ├── AddLootAction.java │ │ │ │ ├── CustomPlayerAction.java │ │ │ │ ├── DropExperienceAction.java │ │ │ │ ├── ExplodeAction.java │ │ │ │ ├── LightningStrikeAction.java │ │ │ │ ├── LootPoolAction.java │ │ │ │ ├── ModifyLootAction.java │ │ │ │ ├── RemoveLootAction.java │ │ │ │ ├── ReplaceLootAction.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── table │ │ │ ├── LootEntriesTransformer.java │ │ │ ├── LootEntryAppender.java │ │ │ ├── LootTableList.java │ │ │ ├── LootTracker.java │ │ │ ├── MutableLootPool.java │ │ │ ├── MutableLootTable.java │ │ │ ├── PostLootAction.java │ │ │ ├── PostLootActionOwner.java │ │ │ └── package-info.java │ │ ├── mixin │ │ ├── CompositeEntryBaseMixin.java │ │ ├── CreeperMixin.java │ │ ├── EntityPredicateMixin.java │ │ ├── HolderSetCodecMixin.java │ │ ├── ItemPredicateMixin.java │ │ ├── LootContextMixin.java │ │ ├── LootItemConditionMixin.java │ │ ├── LootItemConditionalFunctionMixin.java │ │ ├── LootItemFunctionMixin.java │ │ ├── LootParamsBuilderMixin.java │ │ ├── LootParamsMixin.java │ │ ├── LootPoolMixin.java │ │ ├── LootTableMixin.java │ │ ├── ReloadableServerRegistriesMixin.java │ │ ├── SetComponentsFunctionAccessor.java │ │ ├── SkeletonMixin.java │ │ ├── WitherBossMixin.java │ │ ├── ZombieMixin.java │ │ ├── forge │ │ │ ├── CommonHooksMixin.java │ │ │ ├── LootModifierManagerMixin.java │ │ │ └── package-info.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── util │ │ ├── BlockFilter.java │ │ ├── DebugInfo.java │ │ ├── ListHolder.java │ │ ├── LootContextUtils.java │ │ ├── LootObjectList.java │ │ ├── NullableFunction.java │ │ ├── Utils.java │ │ └── package-info.java └── resources │ ├── META-INF │ ├── accesstransformer.cfg │ └── neoforge.mods.toml │ ├── data │ └── lootjs │ │ └── structure │ │ └── empty_test_structure.nbt │ ├── kubejs.plugins.txt │ └── lootjs.mixins.json └── test ├── java └── testmod │ ├── TestMod.java │ ├── event │ ├── Entry.java │ └── package-info.java │ ├── gametest │ ├── ConditionsContainer.java │ ├── GameTestTemplates.java │ ├── GameTestUtils.java │ ├── GameTestUtilsTests.java │ ├── ItemFilterTests.java │ ├── LootActionGameTests.java │ ├── conditions │ │ ├── BiomeCheckTest.java │ │ ├── DimensionCheckTest.java │ │ ├── ExtendedEntityFlagsPredicateTest.java │ │ ├── IsLightLevelTest.java │ │ ├── MatchEquipmentSlotTest.java │ │ ├── MatchKillerDistanceTest.java │ │ └── ParamSetTest.java │ ├── package-info.java │ └── tables │ │ ├── LootTableTests.java │ │ └── package-info.java │ ├── mixin │ └── ScriptManagerMixin.java │ └── package-info.java └── resources ├── META-INF └── neoforge.mods.toml └── testmod.mixins.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable autocrlf on generated files, they always generate with LF 2 | # Add any extra files or paths here to make git stop saying they 3 | # are changed when only line endings change. 4 | src/generated/**/.cache/cache text eol=lf 5 | src/generated/**/*.json text eol=lf 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "1.21.1" 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.1" 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 | -------------------------------------------------------------------------------- /.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: "LootJS" 43 | curseforge_id: "570630" 44 | modrinth_id: "fJFETWDN" 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 | -------------------------------------------------------------------------------- /.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 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | *.log 24 | 25 | # Files from Forge MDK 26 | forge*changelog.txt 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

LootJS

3 | 4 | A [Minecraft] mod for packdevs to easily modify loot tables with [KubeJS]. 5 | 6 | [![Workflow Status][workflow_status_badge]][workflow_status_link] 7 | [![License][license_badge]][license] 8 | 9 | [![Version][version_badge]][version_link] 10 | [![Total Downloads CF][total_downloads_cf_badge]][curseforge] 11 | [![Total Downloads MR][total_downloads_mr_badge]][modrinth] 12 | 13 | [![Discord][discord_badge]][discord] 14 | [![Wiki][wiki_badge]][wiki] 15 | 16 |
17 | 18 | ## **📑 Overview** 19 | 20 | This is a mod for [Minecraft]-[NeoForge] and needs [KubeJS].
21 | 22 | ## **🔧 Installation** 23 | 24 | 1. Download the latest **mod jar** from the [releases], from [CurseForge] or from [Modrinth]. 25 | 2. Download the latest **mod jar** of [KubeJS]. 26 | 3. Install Minecraft [NeoForge]. 27 | 4. Drop both **jar files** into your mods folder. 28 | 29 | ## **🎓 License** 30 | 31 | This project is licensed under the [GNU Lesser General Public License v3.0][license]. 32 | 33 | 34 | 35 | [workflow_status_badge]: https://img.shields.io/github/actions/workflow/status/AlmostReliable/lootjs/build.yml?branch=1.20.1&style=for-the-badge 36 | 37 | [workflow_status_link]: https://github.com/AlmostReliable/lootjs/actions 38 | 39 | [total_downloads_cf_badge]: https://img.shields.io/badge/dynamic/json?color=e04e14&label=CurseForge&style=for-the-badge&query=downloads.total&url=https://api.cfwidget.com/570630&logo=curseforge 40 | 41 | [total_downloads_mr_badge]: https://img.shields.io/modrinth/dt/fJFETWDN?color=5da545&label=Modrinth&style=for-the-badge&logo=modrinth 42 | 43 | [version_badge]: https://img.shields.io/github/v/release/AlmostReliable/lootjs?include_prereleases&style=for-the-badge 44 | 45 | [version_link]: https://github.com/AlmostReliable/lootjs/releases/latest 46 | 47 | [license_badge]: https://img.shields.io/github/license/AlmostReliable/lootjs?style=for-the-badge 48 | 49 | [discord_badge]: https://img.shields.io/discord/917251858974789693?color=5865f2&label=Discord&logo=discord&style=for-the-badge 50 | 51 | [wiki_badge]: https://img.shields.io/badge/Read%20the-Wiki-ba00ff?style=for-the-badge 52 | 53 | 54 | 55 | [minecraft]: https://www.minecraft.net/ 56 | 57 | [kubejs]: https://www.curseforge.com/minecraft/mc-mods/kubejs 58 | 59 | [discord]: https://discord.com/invite/ThFnwZCyYY 60 | 61 | [releases]: https://github.com/AlmostReliable/lootjs/releases 62 | 63 | [curseforge]: https://www.curseforge.com/minecraft/mc-mods/lootjs 64 | 65 | [modrinth]: https://modrinth.com/mod/lootjs 66 | 67 | [neoforge]: https://neoforged.net/ 68 | 69 | [wiki]: https://docs.almostreliable.com/lootjs/ 70 | 71 | [changelog]: CHANGELOG.md 72 | 73 | [license]: LICENSE 74 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.neoforged.moddev") version "2.0.105" 3 | id("com.almostreliable.almostgradle") version "1.3.+" 4 | } 5 | 6 | repositories { 7 | maven("https://maven.latvian.dev/releases") 8 | maven("https://www.cursemaven.com") 9 | maven { 10 | setUrl("https://jitpack.io") 11 | content { 12 | includeGroup("com.github.rtyley") 13 | } 14 | } 15 | } 16 | 17 | almostgradle.setup { 18 | testMod = true 19 | } 20 | 21 | dependencies { 22 | val kubejsVersion: String by project 23 | implementation("dev.latvian.mods:kubejs-neoforge:${kubejsVersion}") 24 | testImplementation("dev.latvian.mods:kubejs-neoforge:${kubejsVersion}") 25 | 26 | testLocalRuntime(almostgradle.recipeViewers.emi.dependency) 27 | } 28 | -------------------------------------------------------------------------------- /example_scripts/create_loot_table.js: -------------------------------------------------------------------------------- 1 | LootJS.lootTables(e => { 2 | e.create("lootjs:blocks/test_table", "block").createPool((pool) => { 3 | pool.addEntry(LootEntry.of("minecraft:stick").limitCount(10, 32)); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /example_scripts/custom_condition.js: -------------------------------------------------------------------------------- 1 | LootJS.lootTables((event) => { 2 | event.getBlockTable('minecraft:coal_ore') 3 | .clear() 4 | .firstPool() 5 | .addEntry(LootEntry.of('minecraft:diamond').matchCustomCondition({ 6 | condition: "minecraft:match_tool", 7 | predicate: { 8 | enchantments: [ 9 | { 10 | enchantment: "minecraft:silk_touch", 11 | levels: { 12 | min: 1, 13 | }, 14 | }, 15 | ], 16 | }, 17 | })); 18 | }) 19 | -------------------------------------------------------------------------------- /example_scripts/holder_sets.js: -------------------------------------------------------------------------------- 1 | LootJS.modifiers(event => { 2 | event.addEntityModifier(["minecraft:cow", "minecraft:sheep"]).addLoot("minecraft:acacia_door") 3 | }) 4 | 5 | LootJS.modifiers(event => { 6 | event.addEntityModifier("minecraft:chicken").addLoot("minecraft:dirt") 7 | }) 8 | 9 | LootJS.modifiers(event => { 10 | event.addEntityModifier("#minecraft:undead").addLoot("minecraft:gravel") 11 | }) 12 | 13 | LootJS.modifiers(event => { 14 | event.addEntityModifier("@minecraft").addLoot("minecraft:stone_stairs") 15 | }) 16 | 17 | LootJS.modifiers(event => { 18 | event.addEntityModifier(/.*spider.*/).addLoot("minecraft:diamond") 19 | }) 20 | -------------------------------------------------------------------------------- /example_scripts/item_filters.js: -------------------------------------------------------------------------------- 1 | LootJS.modifiers(event => { 2 | const entry = LootEntry.testItem("ItemFilter Tag Negate").matchMainHand("!#c:tools") 3 | event.addEntityModifier("minecraft:cow").addLoot(entry) 4 | }) 5 | -------------------------------------------------------------------------------- /example_scripts/loot_table_filter.js: -------------------------------------------------------------------------------- 1 | LootJS.modifiers(event => { 2 | event.addTableModifier(LootType.CHEST, /minecraft:entities.*/).customAction(() => { 3 | console.log("Triggerd LootJS.modifiers for Chests and all entities"); 4 | }) 5 | }); 6 | 7 | LootJS.lootTables(event => { 8 | console.log("All Chest and Entity loot tables: ") 9 | for (let lt of event.modifyLootTables(LootType.CHEST, /minecraft:entities.*/)) { 10 | console.log(" - " + lt.location) 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /example_scripts/predicates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check location check automatic record building 3 | */ 4 | LootJS.modifiers(event => { 5 | const entry = LootEntry.testItem("LocationCheck").matchLocation({ 6 | position: { 7 | x: 30, 8 | y: { 9 | min: 0, 10 | max: 200 11 | }, 12 | z: 30 13 | }, 14 | biomes: "#minecraft:is_overworld", 15 | dimension: "minecraft:overworld", 16 | smokey: false, 17 | light: { 18 | min: 0, 19 | max: 15 20 | }, 21 | block: { 22 | blocks: "#minecraft:mineable/pickaxe", 23 | properties: { 24 | waterlogged: false 25 | } 26 | }, 27 | canSeeSky: true 28 | }) 29 | 30 | event.addEntityModifier("minecraft:chicken").addLoot(entry) 31 | }) 32 | 33 | /** 34 | * Check entity check automatic record building 35 | */ 36 | LootJS.modifiers(event => { 37 | const entry = LootEntry.testItem("EntityPredicate").matchEntity({ 38 | entityType: "@minecraft", 39 | distance: { 40 | absolute: { 41 | min: 0, 42 | max: 100 43 | } 44 | }, 45 | movement: { 46 | speed: { 47 | min: 0, 48 | max: 100 49 | }, 50 | fallDistance: { 51 | min: 0, 52 | max: 100 53 | } 54 | }, 55 | location: { 56 | located: { 57 | // LocationPredicate 58 | }, 59 | steppingOn: { 60 | // LocationPredicate 61 | } 62 | }, 63 | effects: [ 64 | { 65 | id: "minecraft:strength", 66 | duration: { 67 | max: 400 68 | } 69 | } 70 | ], 71 | flags: { 72 | isOnGround: true 73 | }, 74 | equipment: { 75 | // ItemPredicate for each equipment slot 76 | }, 77 | vehicle: { 78 | // EntityPredicate 79 | }, 80 | passenger: { 81 | // EntityPredicate 82 | } 83 | }) 84 | 85 | event.addEntityModifier("minecraft:chicken").addLoot(entry) 86 | }) 87 | 88 | /** 89 | * Damage source test 90 | */ 91 | LootJS.modifiers(event => { 92 | const entry = LootEntry.testItem("DamageSource").matchDamageSource({ 93 | tags: [ 94 | { 95 | expected: true, 96 | id: "minecraft:is_explosion" 97 | }, 98 | ] 99 | }) 100 | 101 | event.addEntityModifier("minecraft:chicken").addLoot(entry) 102 | }) 103 | 104 | /** 105 | * Item predicate test 106 | */ 107 | LootJS.modifiers(event => { 108 | const entry = LootEntry.testItem("ItemPredicate").matchTool({ 109 | items: { 110 | type: "neoforge:or", 111 | values: [ 112 | "@minecraft", 113 | "#c:tools", 114 | /someRegexValue/, 115 | { 116 | type: "neoforge:any" 117 | } 118 | ] 119 | }, 120 | predicates: { 121 | enchantments: [ 122 | { 123 | enchantments: "@minecraft", // Checks if nested holder set still works in predicate 124 | levels: { 125 | min: 3 126 | } 127 | } 128 | ] 129 | } 130 | }) 131 | 132 | event.addEntityModifier("minecraft:chicken").addLoot(entry) 133 | }) 134 | 135 | /** 136 | * Distance predicate test 137 | */ 138 | LootJS.modifiers(event => { 139 | const entry = LootEntry.testItem("DistancePredicate").matchDistance({ 140 | absolute: { 141 | min: 42, 142 | } 143 | }) 144 | 145 | event.addEntityModifier("minecraft:chicken").addLoot(entry) 146 | }) 147 | -------------------------------------------------------------------------------- /example_scripts/print.js: -------------------------------------------------------------------------------- 1 | LootJS.lootTables(event => { 2 | event.getLootTable("minecraft:chests/simple_dungeon").print() 3 | }) 4 | 5 | LootJS.lootTables(event => { 6 | let pools = event.getLootTable("minecraft:chests/simple_dungeon").getPools(); 7 | let hasItem = false; 8 | 9 | for (let pool of pools) { 10 | pool.modifyItemEntry((itemEntry) => { 11 | if (itemEntry.getItem() === "minecraft:gunpowder") { 12 | hasItem = true 13 | } 14 | 15 | return itemEntry; // We don't want to modify 16 | }) 17 | 18 | } 19 | 20 | if (!hasItem) { 21 | throw new Error("Missing gunpowder in LootTable") 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /examples/server_scripts/block/player_equipment.js: -------------------------------------------------------------------------------- 1 | onEvent("lootjs", (event) => { 2 | event 3 | .addBlockLootModifier("#forge:ores") // keep in mind this is a block tag not an item tag 4 | .matchEquip(EquipmentSlot.MAINHAND, Item.of("minecraft:netherite_pickaxe").ignoreNBT()) 5 | .addLoot("minecraft:gravel"); 6 | 7 | // for MainHand and OffHand you can also use: 8 | // matchMainHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) 9 | // matchOffHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) 10 | }); 11 | -------------------------------------------------------------------------------- /examples/server_scripts/disable_loot_modification.js: -------------------------------------------------------------------------------- 1 | onEvent("lootjs", (event) => { 2 | // all leaves disabled per regex 3 | event.disableLootModification(/.*:blocks\/.*_leaves/); 4 | 5 | // disable bats 6 | event.disableLootModification("minecraft:entities/bat"); 7 | }); 8 | -------------------------------------------------------------------------------- /examples/server_scripts/entity/creeper_additional_gunpowder.js: -------------------------------------------------------------------------------- 1 | onEvent("lootjs", (event) => { 2 | event 3 | .addEntityLootModifier("minecraft:creeper") 4 | .randomChance(0.3) // 30% chance 5 | .addLoot("minecraft:gunpowder"); 6 | }); 7 | -------------------------------------------------------------------------------- /examples/server_scripts/entity/double_loot_when_raining.js: -------------------------------------------------------------------------------- 1 | onEvent("lootjs", (event) => { 2 | event 3 | .addLootTypeModifier(LootType.ENTITY) // you also can use multiple types 4 | .logName("It's raining loot") // you can set a custom name for logging 5 | .weatherCheck({ 6 | raining: true, 7 | }) 8 | .modifyLoot(Ingredient.getAll(), (itemStack) => { 9 | // you have to return an item! 10 | return itemStack.withCount(itemStack.getCount() * 2); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/server_scripts/entity/entity_mount.js: -------------------------------------------------------------------------------- 1 | onEvent("lootjs", (event) => { 2 | event 3 | .addLootTypeModifier([LootType.ENTITY]) 4 | .matchEntity((entity) => { 5 | entity.anyType("#skeletons"); 6 | entity.matchMount((mount) => { 7 | mount.anyType("minecraft:spider"); 8 | }); 9 | }) 10 | .addLoot("minecraft:magma_cream"); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/server_scripts/functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Don't mind this function. I use it for some testing. Just use `Item.of()` where you need it! 3 | */ 4 | function itemWithName(item, name) { 5 | return Item.of(item, `{display:{Name:'{\"text\":\"${name}\", \"color\":\"green\"}'}}`); 6 | } 7 | 8 | onEvent("lootjs", (event) => { 9 | event.addEntityLootModifier("minecraft:chicken").functions([Item.of("chicken"), ItemFilter.FOOD], (f) => { 10 | f.smeltLoot(); 11 | }); 12 | }); 13 | 14 | onEvent("lootjs", (event) => { 15 | event 16 | .addBlockLootModifier("minecraft:emerald_block") 17 | .addLoot(itemWithName("iron_sword", "enchantRandom() triggered")) 18 | .enchantRandomly(); 19 | }); 20 | 21 | onEvent("lootjs", (event) => { 22 | event 23 | .addBlockLootModifier("minecraft:emerald_block") 24 | .addLoot(itemWithName("iron_sword", "enchantWithLevels() triggered")) 25 | .enchantWithLevels([2, 3]); 26 | }); 27 | 28 | onEvent("lootjs", (event) => { 29 | event 30 | .addBlockLootModifier("minecraft:emerald_block") 31 | .addLoot(itemWithName("coal_ore", "applyLootingBonus() triggered")) 32 | .applyLootingBonus([3, 10]); 33 | }); 34 | 35 | onEvent("lootjs", (event) => { 36 | event 37 | .addBlockLootModifier("minecraft:emerald_block") 38 | .addLoot(itemWithName("iron_sword", "damage() triggered")) 39 | .damage([0.01, 0.2]); 40 | }); 41 | 42 | onEvent("lootjs", (event) => { 43 | event.addBlockLootModifier("minecraft:emerald_block").pool((p) => { 44 | p.addLoot(itemWithName("minecraft:potion", "addPotion() triggered")); 45 | p.addPotion("poison"); 46 | }); 47 | }); 48 | 49 | onEvent("lootjs", (event) => { 50 | event.addBlockLootModifier("minecraft:emerald_block").pool((p) => { 51 | p.addLoot(itemWithName("minecraft:nether_star", "addLore() triggered")); 52 | p.addLore(["This is a lore", Text.red("This is a red lore")]); 53 | }); 54 | }); 55 | 56 | onEvent("lootjs", (event) => { 57 | event.addBlockLootModifier("minecraft:emerald_block").pool((p) => { 58 | p.addLoot("minecraft:apple"); 59 | p.setName(Text.blue("This is a blue name")); 60 | }); 61 | }); 62 | 63 | onEvent("lootjs", (event) => { 64 | event.addBlockLootModifier("minecraft:emerald_block").pool((p) => { 65 | p.addLoot(Item.of("minecraft:gunpowder", 30)); // should be reduced to 15 66 | p.addLoot(Item.of("minecraft:blaze_powder", 1)); // should be bumped up to at least 5 67 | p.limitCount([5, 10], [15]); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /examples/server_scripts/item_filters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Don't mind this function. I use it for some testing. Just use `Item.of()` where you need it! 3 | */ 4 | function itemWithName(item, name) { 5 | return Item.of(item, `{display:{Name:'{\"text\":\"${name}\", \"color\":\"green\"}'}}`); 6 | } 7 | 8 | onEvent("lootjs", (event) => { 9 | event.enableLogging(); 10 | event 11 | .addBlockLootModifier("minecraft:grass_block") 12 | .matchMainHand(ItemFilter.PICKAXE) 13 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.PICKAXE")); 14 | }); 15 | 16 | onEvent("lootjs", (event) => { 17 | event.enableLogging(); 18 | event 19 | .addBlockLootModifier("minecraft:grass_block") 20 | .matchMainHand(ItemFilter.AXE) 21 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.AXE")); 22 | }); 23 | 24 | onEvent("lootjs", (event) => { 25 | event.enableLogging(); 26 | event 27 | .addBlockLootModifier("minecraft:grass_block") 28 | .matchMainHand(ItemFilter.SWORD) 29 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.SWORD")); 30 | }); 31 | 32 | onEvent("lootjs", (event) => { 33 | event.enableLogging(); 34 | event 35 | .addBlockLootModifier("minecraft:grass_block") 36 | .matchMainHand(ItemFilter.TOOL) 37 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.TOOL")); 38 | }); 39 | 40 | onEvent("lootjs", (event) => { 41 | event 42 | .addBlockLootModifier("minecraft:grass_block") 43 | .matchMainHand(ItemFilter.HAS_TIER) 44 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.HAS_TIER")); 45 | }); 46 | 47 | onEvent("lootjs", (event) => { 48 | event 49 | .addBlockLootModifier("minecraft:grass_block") 50 | .matchMainHand(ItemFilter.PROJECTILE_WEAPON) 51 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.PROJECTILE_WEAPON")); 52 | }); 53 | 54 | onEvent("lootjs", (event) => { 55 | event 56 | .addBlockLootModifier("minecraft:grass_block") 57 | .matchMainHand(ItemFilter.FOOD) 58 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.FOOD")); 59 | }); 60 | 61 | onEvent("lootjs", (event) => { 62 | event 63 | .addBlockLootModifier("minecraft:grass_block") 64 | .matchMainHand(ItemFilter.BLOCK) 65 | .addLoot(itemWithName("paper", "Grass block with ItemFilter.BLOCK")); 66 | }); 67 | -------------------------------------------------------------------------------- /examples/server_scripts/playerAction.js: -------------------------------------------------------------------------------- 1 | 2 | onEvent("lootjs", (event) => { 3 | event.addBlockLootModifier("minecraft:diamond_block").playerAction((player) => { 4 | player.giveExperiencePoints(1000); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /examples/server_scripts/some_conditions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Don't mind this function. I use it for some testing. Just use `Item.of()` where you need it! 3 | */ 4 | function itemWithName(item, name) { 5 | return Item.of(item, `{display:{Name:'{\"text\":\"${name}\", \"color\":\"green\"}'}}`); 6 | } 7 | 8 | onEvent("lootjs", (event) => { 9 | event 10 | .addEntityLootModifier("minecraft:zombie") 11 | .anyStructure("minecraft:stronghold", false) 12 | .addLoot(itemWithName("paper", "Zombie killed in stronghold")); 13 | }); 14 | 15 | onEvent("lootjs", (event) => { 16 | event 17 | .addEntityLootModifier("minecraft:zombie") 18 | .anyBiome("#minecraft:is_forest") 19 | .addLoot(itemWithName("paper", "Zombie killed in forest")); 20 | }); 21 | 22 | onEvent("lootjs", (event) => { 23 | event 24 | .addEntityLootModifier("minecraft:zombie") 25 | .anyBiome("desert") 26 | .addLoot(itemWithName("paper", "Zombie killed in destert")); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group = com.almostreliable 2 | license = GNU Lesser General Public License v3.0 3 | 4 | # Mod options 5 | modId = lootjs 6 | modName = LootJS 7 | modVersion = 3.6.0 8 | modAuthor = AlmostReliable 9 | modDescription = Modify loot through KubeJS. 10 | 11 | # Common 12 | minecraftVersion = 1.21.1 13 | neoforgeVersion = 21.1.209 14 | kubejsVersion = 2101.7.2-build.264 15 | 16 | # AlmostGradle 17 | almostgradle.launchArgs.resizeClient = true 18 | almostgradle.launchArgs.autoWorldJoin = true 19 | 20 | almostgradle.recipeViewers.emi.version = 1.1.22 21 | almostgradle.recipeViewers.emi.minecraftVersion = 1.21.1 22 | 23 | # Parchment 24 | neoForge.parchment.minecraftVersion = 1.21.1 25 | neoForge.parchment.mappingsVersion = 2024.11.17 26 | 27 | # Gradle 28 | org.gradle.jvmargs = -Xmx3G 29 | org.gradle.daemon = false 30 | 31 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlmostReliable/lootjs/21bc16fd6f5e2a75d86f2378f36354d2ad82db41/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase = GRADLE_USER_HOME 2 | distributionPath = wrapper/dists 3 | distributionUrl = https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | zipStoreBase = GRADLE_USER_HOME 5 | zipStorePath = wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/main/java/com/almostreliable/lootjs/LookupProvider.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs; 2 | 3 | import net.minecraft.core.HolderLookup; 4 | import net.minecraft.core.Registry; 5 | import net.minecraft.core.RegistryAccess; 6 | import net.minecraft.resources.ResourceKey; 7 | 8 | import java.util.Optional; 9 | import java.util.stream.Stream; 10 | 11 | public class LookupProvider implements HolderLookup.Provider { 12 | 13 | private final RegistryAccess registryAccess; 14 | 15 | public LookupProvider(RegistryAccess registryAccess) { 16 | this.registryAccess = registryAccess; 17 | } 18 | 19 | @Override 20 | public Stream>> listRegistries() { 21 | return registryAccess.listRegistries(); 22 | } 23 | 24 | @Override 25 | public Optional> lookup(ResourceKey> arg) { 26 | return registryAccess.registry(arg).map(Registry::asTagAddingLookup); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/LootEvents.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs; 2 | 3 | import com.almostreliable.lootjs.loot.extension.LootTableExtension; 4 | import net.minecraft.core.WritableRegistry; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.world.level.storage.loot.LootTable; 7 | import net.neoforged.neoforge.common.loot.IGlobalLootModifier; 8 | 9 | import javax.annotation.Nullable; 10 | import java.util.Map; 11 | import java.util.function.Consumer; 12 | 13 | public class LootEvents { 14 | 15 | @Nullable 16 | private static Consumer> TABLE_EVENT_LISTENERS; 17 | @Nullable 18 | private static Consumer> MODIFIER_EVENT_LISTENERS; 19 | 20 | public static void listen(Consumer> listener) { 21 | if (TABLE_EVENT_LISTENERS == null) { 22 | TABLE_EVENT_LISTENERS = listener; 23 | return; 24 | } 25 | 26 | TABLE_EVENT_LISTENERS = TABLE_EVENT_LISTENERS.andThen(listener); 27 | } 28 | 29 | public static void invoke(WritableRegistry registry) { 30 | if (TABLE_EVENT_LISTENERS != null) { 31 | TABLE_EVENT_LISTENERS.accept(registry); 32 | for (LootTable lootTable : registry) { 33 | ((LootTableExtension) lootTable).lootjs$recompose(); 34 | } 35 | } 36 | } 37 | 38 | public static void listenModifiers(Consumer> listener) { 39 | if (MODIFIER_EVENT_LISTENERS == null) { 40 | MODIFIER_EVENT_LISTENERS = listener; 41 | return; 42 | } 43 | 44 | MODIFIER_EVENT_LISTENERS = MODIFIER_EVENT_LISTENERS.andThen(listener); 45 | } 46 | 47 | public static void invokeModifiers(Map modifiers) { 48 | if (MODIFIER_EVENT_LISTENERS != null) { 49 | MODIFIER_EVENT_LISTENERS.accept(modifiers); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/LootJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs; 2 | 3 | import com.almostreliable.lootjs.core.filters.ItemFilterWrapper; 4 | import net.minecraft.core.HolderLookup; 5 | import net.minecraft.core.Registry; 6 | import net.minecraft.core.RegistryAccess; 7 | import net.minecraft.core.registries.BuiltInRegistries; 8 | import net.minecraft.core.registries.Registries; 9 | import net.minecraft.resources.ResourceKey; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.neoforged.bus.api.IEventBus; 12 | import net.neoforged.fml.common.Mod; 13 | import net.neoforged.neoforge.registries.RegisterEvent; 14 | import org.apache.logging.log4j.LogManager; 15 | import org.apache.logging.log4j.Logger; 16 | 17 | import java.util.Optional; 18 | import java.util.function.Consumer; 19 | import java.util.stream.Stream; 20 | 21 | @Mod(BuildConfig.MOD_ID) 22 | public class LootJS { 23 | public static final Logger LOG = LogManager.getLogger(BuildConfig.MOD_NAME); 24 | public static Consumer DEBUG_ACTION = LOG::info; 25 | 26 | public static HolderLookup.Provider LOOKUP_PROVIDER = new HolderLookup.Provider() { 27 | @Override 28 | public Stream>> listRegistries() { 29 | return Stream.empty(); 30 | } 31 | 32 | @Override 33 | public Optional> lookup(ResourceKey> arg) { 34 | return Optional.empty(); 35 | } 36 | }; 37 | 38 | public LootJS(IEventBus bus) { 39 | bus.addListener(this::onRegister); 40 | LootJSConditions.CONDITIONS.register(bus); 41 | } 42 | 43 | public static HolderLookup.Provider lookup() { 44 | return LOOKUP_PROVIDER; 45 | } 46 | 47 | public static void storeLookup(RegistryAccess lookup) { 48 | LOOKUP_PROVIDER = new LookupProvider(lookup); 49 | } 50 | 51 | /** 52 | * We mainly register the item sub predicate so in case some serialization happens, nothing breaks. But we don't 53 | * really serialize our ItemFilter's as they are dynamic and can't be serialized. 54 | */ 55 | private void onRegister(RegisterEvent event) { 56 | if (event.getRegistry() == BuiltInRegistries.ITEM_SUB_PREDICATE_TYPE) { 57 | event.register(Registries.ITEM_SUB_PREDICATE_TYPE, 58 | ResourceLocation.fromNamespaceAndPath(BuildConfig.MOD_ID, "item"), 59 | () -> ItemFilterWrapper.TYPE); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/LootJSConditions.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs; 2 | 3 | import com.almostreliable.lootjs.core.filters.ItemFilter; 4 | import com.almostreliable.lootjs.loot.condition.*; 5 | import com.mojang.serialization.MapCodec; 6 | import net.minecraft.advancements.critereon.DistancePredicate; 7 | import net.minecraft.advancements.critereon.EntityPredicate; 8 | import net.minecraft.advancements.critereon.MinMaxBounds; 9 | import net.minecraft.core.Holder; 10 | import net.minecraft.core.HolderSet; 11 | import net.minecraft.core.registries.BuiltInRegistries; 12 | import net.minecraft.resources.ResourceLocation; 13 | import net.minecraft.world.entity.EquipmentSlot; 14 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 15 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 16 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 17 | import net.neoforged.neoforge.registries.DeferredRegister; 18 | 19 | /** 20 | * Just exist so we have the types registered. But they should not be used for json stuff anyway. So we just return units. 21 | */ 22 | public class LootJSConditions { 23 | static final DeferredRegister CONDITIONS = DeferredRegister.create(BuiltInRegistries.LOOT_CONDITION_TYPE, 24 | BuildConfig.MOD_ID); 25 | 26 | private static LootItemConditionType create(LootItemCondition unit) { 27 | return new LootItemConditionType(MapCodec.unit(unit)); 28 | } 29 | 30 | public static Holder MATCH_EQUIP = CONDITIONS.register("match_equip", 31 | () -> create(new MatchEquipmentSlot(EquipmentSlot.MAINHAND, ItemFilter.NONE))); 32 | public static Holder MATCH_ANY_INVENTORY_SLOT = CONDITIONS.register( 33 | "match_any_inventory_slot", 34 | () -> create(new MatchAnyInventorySlot(ItemFilter.NONE, false))); 35 | public static Holder DISTANCE = CONDITIONS.register("match_distance", 36 | () -> create(new MatchKillerDistance(DistancePredicate.vertical(MinMaxBounds.Doubles.ANY)))); 37 | public static Holder ANY_STRUCTURE = CONDITIONS.register("match_structure", 38 | () -> create(new MatchStructure(HolderSet.direct(), true))); 39 | public static Holder BIOME = CONDITIONS.register("match_biome", 40 | () -> create(new MatchBiome(HolderSet.direct()))); 41 | public static Holder LIGHT_LEVEL = CONDITIONS.register("light_level", 42 | () -> create(new IsLightLevel(-1, -1))); 43 | public static Holder ANY_DIMENSION = CONDITIONS.register("match_dimension", 44 | () -> create(new MatchDimension(new ResourceLocation[]{}))); 45 | public static Holder PARAM = CONDITIONS.register("param", 46 | () -> create(new CustomParamPredicate<>(LootContextParams.THIS_ENTITY, entity -> false))); 47 | public static Holder PLAYER_PARAM = CONDITIONS.register("player_param", 48 | () -> create(new PlayerParamPredicate(p -> false))); 49 | public static Holder MATCH_PLAYER = CONDITIONS.register("match_player", 50 | () -> create(new MatchPlayer(EntityPredicate.Builder.entity().build()))); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/LootModificationsAPI.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.core.LootContextInfo; 5 | import com.almostreliable.lootjs.core.filters.IdFilter; 6 | import com.almostreliable.lootjs.loot.modifier.LootModifier; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.item.ItemStack; 9 | import net.minecraft.world.level.storage.loot.LootContext; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | public class LootModificationsAPI { 16 | 17 | public static final List FILTERS = new ArrayList<>(); 18 | private static final List modifiers = new ArrayList<>(); 19 | public static boolean DISABLE_WITHER_DROPPING_NETHER_STAR = false; 20 | public static boolean DISABLE_ZOMBIE_DROPPING_HEAD = false; 21 | public static boolean DISABLE_SKELETON_DROPPING_HEAD = false; 22 | public static boolean DISABLE_CREEPER_DROPPING_HEAD = false; 23 | public static boolean DEBUG_LOOT_MODIFIERS = false; 24 | 25 | private LootModificationsAPI() { 26 | } 27 | 28 | public static void reload() { 29 | modifiers.clear(); 30 | DEBUG_LOOT_MODIFIERS = false; 31 | FILTERS.clear(); 32 | FILTERS.add(new IdFilter.ByLocation(ResourceLocation.parse("minecraft:blocks/fire"))); 33 | } 34 | 35 | public List modifiers() { 36 | return Collections.unmodifiableList(modifiers); 37 | } 38 | 39 | public static void invokeActions(List loot, LootContext context) { 40 | for (IdFilter filter : FILTERS) { 41 | if (filter.test(context.getQueriedLootTableId())) { 42 | return; 43 | } 44 | } 45 | 46 | context.getLevel().getProfiler().push("LootModificationsAPI::invokeActions"); 47 | 48 | // TODO more testing here. I don't really know why there are empty items in the list or better: 49 | // TODO There are items which refer to the correct item but their cache flag is true so it acts like air 50 | loot.removeIf(ItemStack::isEmpty); 51 | 52 | LootBucket lootBucket = new LootBucket(context, loot); 53 | 54 | if (isDebug()) { 55 | LootContextInfo lootContextInfo = LootContextInfo.create(context, lootBucket); 56 | runModifiers(context, lootBucket); 57 | lootContextInfo.updateLootAfter(lootBucket); 58 | StringBuilder stringBuilder = new StringBuilder(); 59 | stringBuilder.append("\n"); 60 | lootContextInfo.release(stringBuilder); 61 | LootJS.DEBUG_ACTION.accept(stringBuilder.toString()); 62 | } else { 63 | runModifiers(context, lootBucket); 64 | } 65 | 66 | context.getLevel().getProfiler().pop(); 67 | } 68 | 69 | private static void runModifiers(LootContext context, LootBucket lootBucket) { 70 | for (var modification : modifiers) { 71 | modification.run(context, lootBucket); 72 | } 73 | } 74 | 75 | public static void addModification(LootModifier modifier) { 76 | modifiers.add(modifier); 77 | } 78 | 79 | public static boolean isDebug() { 80 | return DEBUG_LOOT_MODIFIERS; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/PlatformLoader.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs; 2 | 3 | import java.util.ServiceLoader; 4 | 5 | public class PlatformLoader { 6 | static T load(Class clazz) { 7 | final T loadedService = ServiceLoader.load(clazz) 8 | .findFirst() 9 | .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); 10 | LootJS.LOG.debug("Loaded {} for service {}", loadedService, clazz); 11 | return loadedService; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/LootConditionTypes.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core; 2 | 3 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 4 | 5 | public class LootConditionTypes { 6 | public static final LootItemConditionType UNUSED = create(); 7 | 8 | private static LootItemConditionType create() { 9 | // TODO 10 | return new LootItemConditionType(null); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/LootType.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core; 2 | 3 | import net.minecraft.Util; 4 | import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet; 5 | import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public enum LootType { 11 | UNKNOWN(new LootContextParamSet.Builder().build()), 12 | BLOCK(LootContextParamSets.BLOCK), 13 | BLOCK_USE(LootContextParamSets.BLOCK_USE), 14 | CHEST(LootContextParamSets.CHEST), 15 | FISHING(LootContextParamSets.FISHING), 16 | ENTITY(LootContextParamSets.ENTITY), 17 | EQUIPMENT(LootContextParamSets.EQUIPMENT), 18 | ARCHAEOLOGY(LootContextParamSets.ARCHAEOLOGY), 19 | GIFT(LootContextParamSets.GIFT), 20 | VAULT(LootContextParamSets.VAULT), 21 | PIGLIN_BARTER(LootContextParamSets.PIGLIN_BARTER), 22 | ADVANCEMENT_REWARD(LootContextParamSets.ADVANCEMENT_REWARD), 23 | ADVANCEMENT_ENTITY(LootContextParamSets.ADVANCEMENT_ENTITY), 24 | ADVANCEMENT_LOCATION(LootContextParamSets.ADVANCEMENT_LOCATION), 25 | COMMAND(LootContextParamSets.COMMAND), 26 | SELECTOR(LootContextParamSets.SELECTOR), 27 | SHEARING(LootContextParamSets.SHEARING), 28 | GENERIC(LootContextParamSets.ALL_PARAMS); 29 | 30 | private static final Map MAPPINGS = Util.make(new HashMap<>(), (m) -> { 31 | for (LootType lootType : values()) { 32 | m.put(lootType.getParamSet(), lootType); 33 | } 34 | }); 35 | 36 | public static LootType getLootType(LootContextParamSet paramSet) { 37 | return MAPPINGS.getOrDefault(paramSet, LootType.UNKNOWN); 38 | } 39 | 40 | private final LootContextParamSet paramSet; 41 | 42 | LootType(LootContextParamSet paramSet) { 43 | this.paramSet = paramSet; 44 | } 45 | 46 | public LootContextParamSet getParamSet() { 47 | return this.paramSet; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/AbstractSimpleLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import com.almostreliable.lootjs.loot.LootConditionList; 4 | import com.almostreliable.lootjs.loot.LootFunctionList; 5 | import com.almostreliable.lootjs.util.DebugInfo; 6 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntryType; 7 | import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer; 8 | 9 | import javax.annotation.Nullable; 10 | 11 | public abstract class AbstractSimpleLootEntry implements SimpleLootEntry { 12 | 13 | protected final E vanillaEntry; 14 | @Nullable 15 | private LootConditionList conditions; 16 | @Nullable 17 | private LootFunctionList functions; 18 | 19 | public AbstractSimpleLootEntry(E vanillaEntry) { 20 | this.vanillaEntry = vanillaEntry; 21 | } 22 | 23 | public AbstractSimpleLootEntry(E vanillaEntry, LootConditionList conditions, LootFunctionList functions) { 24 | this(vanillaEntry); 25 | this.conditions = conditions; 26 | this.functions = functions; 27 | } 28 | 29 | @Override 30 | public LootPoolEntryType getVanillaType() { 31 | return vanillaEntry.getType(); 32 | } 33 | 34 | @Override 35 | public E getVanillaEntry() { 36 | return vanillaEntry; 37 | } 38 | 39 | public LootFunctionList getFunctions() { 40 | if (functions == null) { 41 | functions = new LootFunctionList(vanillaEntry.functions); 42 | vanillaEntry.functions = functions.getElements(); 43 | vanillaEntry.compositeFunction = functions; 44 | } 45 | 46 | return functions; 47 | } 48 | 49 | @Override 50 | public LootConditionList getConditions() { 51 | if (conditions == null) { 52 | conditions = new LootConditionList(vanillaEntry.conditions); 53 | vanillaEntry.conditions = conditions.getElements(); 54 | vanillaEntry.compositeCondition = conditions; 55 | } 56 | 57 | return conditions; 58 | } 59 | 60 | public void setWeight(int weight) { 61 | vanillaEntry.weight = Math.max(1, weight); 62 | } 63 | 64 | public int getWeight() { 65 | return vanillaEntry.weight; 66 | } 67 | 68 | public void setQuality(int quality) { 69 | vanillaEntry.quality = Math.max(0, quality); 70 | } 71 | 72 | public int getQuality() { 73 | return vanillaEntry.quality; 74 | } 75 | 76 | @Override 77 | public void collectDebugInfo(DebugInfo info) { 78 | info.push(); 79 | when(conditions -> conditions.collectDebugInfo(info)); 80 | getFunctions().collectDebugInfo(info); 81 | info.pop(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/CompositeLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import com.almostreliable.lootjs.loot.LootConditionList; 4 | import com.almostreliable.lootjs.loot.LootEntryList; 5 | import com.almostreliable.lootjs.loot.extension.CompositeEntryBaseExtension; 6 | import com.almostreliable.lootjs.loot.table.LootEntriesTransformer; 7 | import com.almostreliable.lootjs.loot.table.LootEntryAppender; 8 | import com.almostreliable.lootjs.util.DebugInfo; 9 | import net.minecraft.world.level.storage.loot.entries.CompositeEntryBase; 10 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntryType; 11 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 12 | 13 | import javax.annotation.Nullable; 14 | import java.util.function.Consumer; 15 | import java.util.function.Predicate; 16 | import java.util.function.UnaryOperator; 17 | 18 | public class CompositeLootEntry implements LootEntry, LootEntriesTransformer, LootEntryAppender { 19 | private final CompositeEntryBase vanillaEntry; 20 | @Nullable 21 | private LootEntryList entries; 22 | @Nullable 23 | private LootConditionList conditions; 24 | 25 | public CompositeLootEntry(CompositeEntryBase vanillaEntry) { 26 | this.vanillaEntry = vanillaEntry; 27 | } 28 | 29 | public CompositeLootEntry(CompositeEntryBase vanillaEntry, LootEntryList entries, LootConditionList conditions) { 30 | this.vanillaEntry = vanillaEntry; 31 | this.entries = entries; 32 | this.conditions = conditions; 33 | } 34 | 35 | public LootEntryList getEntries() { 36 | if (entries == null) { 37 | entries = new LootEntryList(((CompositeEntryBaseExtension) vanillaEntry).lootjs$getEntries()); 38 | } 39 | 40 | return entries; 41 | } 42 | 43 | public CompositeLootEntry entries(Consumer callback) { 44 | callback.accept(getEntries()); 45 | return this; 46 | } 47 | 48 | @Override 49 | public LootPoolEntryType getVanillaType() { 50 | return vanillaEntry.getType(); 51 | } 52 | 53 | @Override 54 | public CompositeEntryBase getVanillaEntry() { 55 | return vanillaEntry; 56 | } 57 | 58 | @Override 59 | public CompositeLootEntry when(Consumer callback) { 60 | callback.accept(getConditions()); 61 | return this; 62 | } 63 | 64 | @Override 65 | public LootConditionList getConditions() { 66 | if (conditions == null) { 67 | conditions = new LootConditionList(vanillaEntry.conditions); 68 | vanillaEntry.conditions = conditions.getElements(); 69 | vanillaEntry.compositeCondition = conditions; 70 | } 71 | 72 | return conditions; 73 | } 74 | 75 | @Override 76 | public void collectDebugInfo(DebugInfo info) { 77 | info.add(getType().toString()); 78 | info.push(); 79 | entries(entries -> entries.collectDebugInfo(info)); 80 | when(conditions -> conditions.collectDebugInfo(info)); 81 | info.pop(); 82 | } 83 | 84 | @Override 85 | public CompositeLootEntry addEntry(LootEntry entry) { 86 | entries(entries -> entries.add(entry)); 87 | return this; 88 | } 89 | 90 | @Override 91 | public CompositeLootEntry modifyEntry(UnaryOperator onTransform, boolean deepTransform) { 92 | getEntries().modifyEntry(onTransform, deepTransform); 93 | return this; 94 | } 95 | 96 | @Override 97 | public CompositeLootEntry removeEntry(Predicate onRemove, boolean deepRemove) { 98 | getEntries().removeEntry(onRemove, deepRemove); 99 | return this; 100 | } 101 | 102 | @Override 103 | public CompositeLootEntry addCondition(LootItemCondition condition) { 104 | getConditions().add(condition); 105 | return this; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/DynamicLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import net.minecraft.resources.ResourceLocation; 4 | import net.minecraft.world.level.storage.loot.entries.DynamicLoot; 5 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 6 | 7 | public class DynamicLootEntry extends AbstractSimpleLootEntry { 8 | public DynamicLootEntry(DynamicLoot vanillaEntry) { 9 | super(vanillaEntry); 10 | } 11 | 12 | public ResourceLocation getLocation() { 13 | return vanillaEntry.name; 14 | } 15 | 16 | public void setLocation(ResourceLocation reference) { 17 | vanillaEntry.name = reference; 18 | } 19 | 20 | @Override 21 | public DynamicLootEntry addCondition(LootItemCondition condition) { 22 | getConditions().add(condition); 23 | return this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/EmptyLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import com.almostreliable.lootjs.util.DebugInfo; 4 | import net.minecraft.world.level.storage.loot.entries.EmptyLootItem; 5 | import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer; 6 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 7 | 8 | public class EmptyLootEntry extends AbstractSimpleLootEntry implements SingleLootEntry { 9 | public EmptyLootEntry(EmptyLootItem vanillaEntry) { 10 | super(vanillaEntry); 11 | } 12 | 13 | public EmptyLootEntry() { 14 | super(new EmptyLootItem( 15 | LootPoolSingletonContainer.DEFAULT_WEIGHT, 16 | LootPoolSingletonContainer.DEFAULT_QUALITY, 17 | EMPTY_CONDITIONS, 18 | EMPTY_FUNCTIONS)); 19 | } 20 | 21 | @Override 22 | public EmptyLootEntry addCondition(LootItemCondition condition) { 23 | getConditions().add(condition); 24 | return this; 25 | } 26 | 27 | @Override 28 | public void collectDebugInfo(DebugInfo info) { 29 | info.add("% Empty"); 30 | super.collectDebugInfo(info); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/ItemLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import com.almostreliable.lootjs.core.filters.ItemFilter; 4 | import com.almostreliable.lootjs.mixin.SetComponentsFunctionAccessor; 5 | import com.almostreliable.lootjs.util.DebugInfo; 6 | import com.almostreliable.lootjs.util.Utils; 7 | import net.minecraft.core.component.DataComponentPatch; 8 | import net.minecraft.core.component.TypedDataComponent; 9 | import net.minecraft.core.registries.BuiltInRegistries; 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.level.storage.loot.LootContext; 14 | import net.minecraft.world.level.storage.loot.entries.LootItem; 15 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntries; 16 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntryType; 17 | import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer; 18 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 19 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 20 | import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; 21 | import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; 22 | 23 | import javax.annotation.Nullable; 24 | import java.util.ArrayList; 25 | 26 | public class ItemLootEntry extends AbstractSimpleLootEntry implements SingleLootEntry { 27 | 28 | public ItemLootEntry(LootItem vanillaEntry) { 29 | super(vanillaEntry); 30 | } 31 | 32 | public ItemLootEntry(ItemStack itemStack) { 33 | super(new LootItem(itemStack.getItem().builtInRegistryHolder(), 34 | LootPoolSingletonContainer.DEFAULT_WEIGHT, 35 | LootPoolSingletonContainer.DEFAULT_QUALITY, 36 | EMPTY_CONDITIONS, 37 | EMPTY_FUNCTIONS)); 38 | 39 | if (itemStack.getCount() > 1) { 40 | getFunctions().setCount(ConstantValue.exactly(itemStack.getCount())); 41 | } 42 | 43 | if (!itemStack.isComponentsPatchEmpty()) { 44 | DataComponentPatch.Builder builder = DataComponentPatch.builder(); 45 | for (TypedDataComponent component : itemStack.getComponents()) { 46 | builder.set(Utils.cast(component.type()), component.value()); 47 | } 48 | 49 | var function = SetComponentsFunctionAccessor.lootjs$create(new ArrayList<>(), builder.build()); 50 | getFunctions().addFunction(function); 51 | } 52 | } 53 | 54 | public ItemLootEntry(Item item, @Nullable NumberProvider count) { 55 | super(new LootItem(item.builtInRegistryHolder(), 56 | LootPoolSingletonContainer.DEFAULT_WEIGHT, 57 | LootPoolSingletonContainer.DEFAULT_QUALITY, 58 | EMPTY_CONDITIONS, 59 | EMPTY_FUNCTIONS)); 60 | 61 | if (count != null) { 62 | getFunctions().setCount(count); 63 | } 64 | } 65 | 66 | @Override 67 | public LootPoolEntryType getVanillaType() { 68 | return LootPoolEntries.ITEM; 69 | } 70 | 71 | public Item getItem() { 72 | return vanillaEntry.item.value(); 73 | } 74 | 75 | public void setItem(Item item) { 76 | if (item == Items.AIR) { 77 | throw new IllegalStateException("Vanilla Loot Entry cannot be set to AIR, consider using LootEntry.empty()"); 78 | } 79 | 80 | vanillaEntry.item = item.builtInRegistryHolder(); 81 | } 82 | 83 | @Nullable 84 | public ItemStack create(LootContext context) { 85 | for (LootItemCondition condition : getConditions()) { 86 | if (!condition.test(context)) { 87 | return null; 88 | } 89 | } 90 | 91 | var item = new ItemStack(getItem()); 92 | 93 | for (LootItemFunction function : getFunctions()) { 94 | item = function.apply(item, context); 95 | } 96 | 97 | return item; 98 | } 99 | 100 | public boolean test(ItemFilter filter) { 101 | return filter.test(new ItemStack(getItem())); 102 | } 103 | 104 | @Override 105 | public ItemLootEntry addCondition(LootItemCondition condition) { 106 | getConditions().add(condition); 107 | return this; 108 | } 109 | 110 | @Override 111 | public void collectDebugInfo(DebugInfo info) { 112 | info.add("% Item: " + BuiltInRegistries.ITEM.getKey(getItem())); 113 | super.collectDebugInfo(info); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/SimpleLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import com.almostreliable.lootjs.loot.LootConditionList; 4 | import com.almostreliable.lootjs.loot.LootFunctionList; 5 | import com.almostreliable.lootjs.loot.LootFunctionsContainer; 6 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 7 | import net.minecraft.world.level.storage.loot.functions.LootItemFunctions; 8 | import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction; 9 | import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; 10 | 11 | import java.util.function.Consumer; 12 | 13 | public interface SimpleLootEntry extends LootEntry, LootFunctionsContainer { 14 | 15 | LootFunctionList getFunctions(); 16 | 17 | @Override 18 | default SimpleLootEntry when(Consumer callback) { 19 | callback.accept(getConditions()); 20 | return this; 21 | } 22 | 23 | default SimpleLootEntry apply(Consumer callback) { 24 | callback.accept(getFunctions()); 25 | return this; 26 | } 27 | 28 | @Override 29 | default SimpleLootEntry addFunction(LootItemFunction lootItemFunction) { 30 | getFunctions().add(lootItemFunction); 31 | return this; 32 | } 33 | 34 | default SimpleLootEntry setCount(NumberProvider numberProvider) { 35 | LootItemFunction sc = SetItemCountFunction.setCount(numberProvider).build(); 36 | if (!getFunctions().replace(LootItemFunctions.SET_COUNT, sc)) { 37 | getFunctions().add(sc); 38 | } 39 | 40 | return this; 41 | } 42 | 43 | default SimpleLootEntry withWeight(int weight) { 44 | setWeight(weight); 45 | return this; 46 | } 47 | 48 | void setWeight(int weight); 49 | 50 | int getWeight(); 51 | 52 | default SimpleLootEntry withQuality(int quality) { 53 | setQuality(quality); 54 | return this; 55 | } 56 | 57 | void setQuality(int quality); 58 | 59 | int getQuality(); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/SingleLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | public interface SingleLootEntry extends SimpleLootEntry { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/TableReferenceLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import com.almostreliable.lootjs.util.DebugInfo; 4 | import com.mojang.datafixers.util.Either; 5 | import net.minecraft.core.registries.Registries; 6 | import net.minecraft.resources.ResourceKey; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.level.storage.loot.LootTable; 9 | import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer; 10 | import net.minecraft.world.level.storage.loot.entries.NestedLootTable; 11 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 12 | 13 | public class TableReferenceLootEntry extends AbstractSimpleLootEntry { 14 | public TableReferenceLootEntry(NestedLootTable vanillaEntry) { 15 | super(vanillaEntry); 16 | } 17 | 18 | public TableReferenceLootEntry(ResourceLocation location) { 19 | super(new NestedLootTable(Either.left(ResourceKey.create(Registries.LOOT_TABLE, location)), 20 | LootPoolSingletonContainer.DEFAULT_WEIGHT, 21 | LootPoolSingletonContainer.DEFAULT_QUALITY, 22 | EMPTY_CONDITIONS, 23 | EMPTY_FUNCTIONS)); 24 | } 25 | 26 | public ResourceLocation getLocation() { 27 | return vanillaEntry.contents.map(ResourceKey::location, LootTable::getLootTableId); 28 | } 29 | 30 | public void setLocation(ResourceLocation reference) { 31 | vanillaEntry.contents = Either.left(ResourceKey.create(Registries.LOOT_TABLE, reference)); 32 | } 33 | 34 | @Override 35 | public TableReferenceLootEntry addCondition(LootItemCondition condition) { 36 | getConditions().add(condition); 37 | return this; 38 | } 39 | 40 | @Override 41 | public void collectDebugInfo(DebugInfo info) { 42 | info.add("% Table: " + getLocation()); 43 | super.collectDebugInfo(info); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/TagLootEntry.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.entry; 2 | 3 | import com.almostreliable.lootjs.util.DebugInfo; 4 | import net.minecraft.core.registries.Registries; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.tags.TagKey; 7 | import net.minecraft.world.item.Item; 8 | import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer; 9 | import net.minecraft.world.level.storage.loot.entries.TagEntry; 10 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 11 | 12 | public class TagLootEntry extends AbstractSimpleLootEntry implements SingleLootEntry { 13 | 14 | public TagLootEntry(TagEntry vanillaEntry) { 15 | super(vanillaEntry); 16 | } 17 | 18 | public TagLootEntry(TagKey tag, boolean expand) { 19 | super(new TagEntry(tag, 20 | expand, 21 | LootPoolSingletonContainer.DEFAULT_WEIGHT, 22 | LootPoolSingletonContainer.DEFAULT_QUALITY, 23 | EMPTY_CONDITIONS, 24 | EMPTY_FUNCTIONS)); 25 | } 26 | 27 | public boolean getExpand() { 28 | return vanillaEntry.expand; 29 | } 30 | 31 | public void setExpand(boolean expand) { 32 | vanillaEntry.expand = expand; 33 | } 34 | 35 | public String getTag() { 36 | return vanillaEntry.tag.location().toString(); 37 | } 38 | 39 | public void setTag(String tag) { 40 | if (tag.startsWith("#")) { 41 | tag = tag.substring(1); 42 | } 43 | 44 | vanillaEntry.tag = TagKey.create(Registries.ITEM, ResourceLocation.parse(tag)); 45 | } 46 | 47 | public boolean isTag(String tag) { 48 | if (tag.startsWith("#")) { 49 | tag = tag.substring(1); 50 | } 51 | 52 | return tag.equals(getTag()); 53 | } 54 | 55 | @Override 56 | public TagLootEntry addCondition(LootItemCondition condition) { 57 | getConditions().add(condition); 58 | return this; 59 | } 60 | 61 | @Override 62 | public void collectDebugInfo(DebugInfo info) { 63 | info.add("% Tag: " + getTag()); 64 | super.collectDebugInfo(info); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/entry/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.core.entry; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/filters/IdFilter.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.filters; 2 | 3 | import net.minecraft.resources.ResourceLocation; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.List; 7 | import java.util.function.Predicate; 8 | import java.util.regex.Pattern; 9 | 10 | public interface IdFilter extends Predicate { 11 | record ByLocation(ResourceLocation location) implements IdFilter { 12 | @Override 13 | public boolean test(ResourceLocation resourceLocation) { 14 | return location.equals(resourceLocation); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "Id[" + location + "]"; 20 | } 21 | } 22 | 23 | record ByPattern(Pattern pattern) implements IdFilter { 24 | @Override 25 | public boolean test(ResourceLocation resourceLocation) { 26 | return pattern.matcher(resourceLocation.toString()).matches(); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "Pattern[" + pattern.pattern() + "]"; 32 | } 33 | } 34 | 35 | record ByMod(String mod) implements IdFilter { 36 | 37 | @Override 38 | public boolean test(ResourceLocation resourceLocation) { 39 | return resourceLocation.getNamespace().equals(mod); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Mod[" + mod + "]"; 45 | } 46 | } 47 | 48 | record Or(List filters) implements IdFilter { 49 | @Override 50 | public boolean test(ResourceLocation resourceLocation) { 51 | return filters.stream().anyMatch(filter -> filter.test(resourceLocation)); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "Or[" + StringUtils.join(filters, ", ") + "]"; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/filters/ItemFilterWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.filters; 2 | 3 | import com.mojang.serialization.Codec; 4 | import net.minecraft.advancements.critereon.ItemSubPredicate; 5 | import net.minecraft.world.item.ItemStack; 6 | 7 | public record ItemFilterWrapper(ItemFilter filter) implements ItemSubPredicate, ItemFilter { 8 | public static final Codec CODEC = Codec.unit(() -> new ItemFilterWrapper(ItemFilter.NONE)); 9 | public static final Type TYPE = new Type<>(ItemFilterWrapper.CODEC); 10 | 11 | @Override 12 | public boolean matches(ItemStack itemStack) { 13 | return filter.test(itemStack); 14 | } 15 | 16 | @Override 17 | public boolean test(ItemStack itemStack) { 18 | return filter.test(itemStack); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/filters/LootTableFilter.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.core.filters; 2 | 3 | import com.almostreliable.lootjs.core.LootType; 4 | import com.almostreliable.lootjs.loot.extension.LootContextExtension; 5 | import net.minecraft.world.level.storage.loot.LootContext; 6 | import net.minecraft.world.level.storage.loot.LootTable; 7 | 8 | public interface LootTableFilter { 9 | 10 | boolean test(LootTable table); 11 | 12 | boolean test(LootContext context); 13 | 14 | record ByIdFilter(IdFilter filter) implements LootTableFilter { 15 | 16 | @Override 17 | public boolean test(LootTable table) { 18 | return filter.test(table.getLootTableId()); 19 | } 20 | 21 | @Override 22 | public boolean test(LootContext context) { 23 | return filter.test(context.getQueriedLootTableId()); 24 | } 25 | } 26 | 27 | record ByLootType(LootType type) implements LootTableFilter { 28 | 29 | @Override 30 | public boolean test(LootTable lootTable) { 31 | return type == LootType.getLootType(lootTable.getParamSet()); 32 | } 33 | 34 | @Override 35 | public boolean test(LootContext context) { 36 | return LootContextExtension.cast(context).lootjs$isType(type); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/filters/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.core.filters; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/core/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.core; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/KubeOps.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube; 2 | 3 | import com.almostreliable.lootjs.LookupProvider; 4 | import dev.latvian.mods.kubejs.util.RegistryAccessContainer; 5 | import net.minecraft.resources.RegistryOps; 6 | 7 | public class KubeOps extends RegistryOps { 8 | 9 | public static KubeOps create(RegistryAccessContainer reggistries) { 10 | return new KubeOps(RegistryOps.create(reggistries.java(), new LookupProvider(reggistries.access()))); 11 | } 12 | 13 | public KubeOps(RegistryOps dynamicOps) { 14 | super(dynamicOps); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/LootJSEvent.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube; 2 | 3 | import dev.latvian.mods.kubejs.event.EventGroup; 4 | import dev.latvian.mods.kubejs.event.EventHandler; 5 | 6 | public interface LootJSEvent { 7 | EventGroup GROUP = EventGroup.of("LootJS"); 8 | EventHandler MODIFIERS = GROUP.server("modifiers", () -> LootModificationEventJS.class); 9 | EventHandler LOOT_TABLES = GROUP.server("lootTables", () -> LootTableEventJS.class); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/LootModificationEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube; 2 | 3 | import com.almostreliable.lootjs.loot.LootModificationEvent; 4 | import dev.latvian.mods.kubejs.event.EventResult; 5 | import dev.latvian.mods.kubejs.event.KubeEvent; 6 | import dev.latvian.mods.kubejs.script.ConsoleJS; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.neoforged.neoforge.common.loot.IGlobalLootModifier; 9 | 10 | import java.util.Map; 11 | import java.util.stream.Collectors; 12 | 13 | public class LootModificationEventJS extends LootModificationEvent implements KubeEvent { 14 | 15 | public LootModificationEventJS(Map modifiers) { 16 | super(modifiers); 17 | } 18 | 19 | @Override 20 | public void afterPosted(EventResult result) { 21 | if (!removedGlobalModifiers.isEmpty()) { 22 | ConsoleJS.SERVER.info("[LootJS] Removed " + removedGlobalModifiers.size() + " global loot modifiers: " + 23 | removedGlobalModifiers 24 | .stream() 25 | .map(ResourceLocation::toString) 26 | .collect(Collectors.joining(", "))); 27 | } 28 | 29 | storeModifiers(throwable -> ConsoleJS.SERVER.error(throwable)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/LootTableEventJS.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube; 2 | 3 | import com.almostreliable.lootjs.loot.LootTableEvent; 4 | import dev.latvian.mods.kubejs.event.EventResult; 5 | import dev.latvian.mods.kubejs.event.KubeEvent; 6 | import net.minecraft.core.WritableRegistry; 7 | import net.minecraft.world.level.storage.loot.LootTable; 8 | 9 | public class LootTableEventJS extends LootTableEvent implements KubeEvent { 10 | 11 | public LootTableEventJS(WritableRegistry registry) { 12 | super(registry); 13 | } 14 | 15 | @Override 16 | public void afterPosted(EventResult result) { 17 | // TODO freeze somehow everything? 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.kube; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/ItemFilterWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube.wrappers; 2 | 3 | import com.almostreliable.lootjs.core.filters.ItemFilter; 4 | import com.almostreliable.lootjs.core.filters.ItemFilterImpl; 5 | import dev.latvian.mods.kubejs.plugin.builtin.wrapper.IngredientWrapper; 6 | import dev.latvian.mods.rhino.Context; 7 | import net.minecraft.core.registries.BuiltInRegistries; 8 | import net.minecraft.core.registries.Registries; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.tags.TagKey; 11 | import net.minecraft.world.item.Item; 12 | import net.minecraft.world.item.crafting.Ingredient; 13 | 14 | import javax.annotation.Nullable; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class ItemFilterWrapper { 19 | 20 | private static ItemFilter ofItemFilterSingle(Context cx, @Nullable Object o) { 21 | if (o instanceof ItemFilter i) return i; 22 | 23 | if (o instanceof String str && !str.isEmpty()) { 24 | String first = str.substring(0, 1); 25 | String remaining = str.substring(1); 26 | switch (first) { 27 | case "*": 28 | return ItemFilter.ANY; 29 | case "#": 30 | ResourceLocation location = ResourceLocation.parse(remaining); 31 | TagKey tag = TagKey.create(Registries.ITEM, location); 32 | return new ItemFilterImpl.ByTag(tag); 33 | case "@": 34 | return itemStack -> { 35 | var key = BuiltInRegistries.ITEM.getKey(itemStack.getItem()); 36 | return key.getNamespace().equals(remaining); 37 | }; 38 | case "!": 39 | return ofItemFilterSingle(cx, remaining.trim()).negate(); 40 | } 41 | } 42 | 43 | Ingredient ingredient = IngredientWrapper.wrap(cx, o); 44 | if (ingredient.isEmpty()) { 45 | return ItemFilter.EMPTY; 46 | } 47 | 48 | return new ItemFilterImpl.ByIngredient(ingredient); 49 | } 50 | 51 | public static ItemFilter ofItemFilter(Context cx, Object o) { 52 | if (o instanceof List list) { 53 | List filters = new ArrayList<>(list.size()); 54 | for (Object entry : list) { 55 | var filter = ofItemFilter(cx, entry); 56 | filters.add(filter); 57 | } 58 | 59 | return itemStack -> { 60 | for (ItemFilter filter : filters) { 61 | if (filter.test(itemStack)) { 62 | return true; 63 | } 64 | } 65 | 66 | return false; 67 | }; 68 | } 69 | 70 | return ofItemFilterSingle(cx, o); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/ItemPredicateWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube.wrappers; 2 | 3 | import com.almostreliable.lootjs.core.filters.ItemFilter; 4 | import com.almostreliable.lootjs.core.filters.ItemFilterWrapper; 5 | import com.almostreliable.lootjs.kube.KubeOps; 6 | import dev.latvian.mods.kubejs.script.KubeJSContext; 7 | import dev.latvian.mods.kubejs.util.RegistryAccessContainer; 8 | import dev.latvian.mods.rhino.Context; 9 | import dev.latvian.mods.rhino.type.TypeInfo; 10 | import net.minecraft.advancements.critereon.ItemPredicate; 11 | import net.minecraft.advancements.critereon.MinMaxBounds; 12 | import net.minecraft.core.HolderSet; 13 | import net.minecraft.core.component.DataComponentPredicate; 14 | import net.minecraft.world.item.Item; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.Optional; 19 | 20 | public class ItemPredicateWrapper { 21 | 22 | private static final TypeInfo ITEM_HOLDER_SET = TypeInfo.of(HolderSet.class).withParams(TypeInfo.of(Item.class)); 23 | 24 | @SuppressWarnings("unchecked") 25 | public static ItemPredicate of(Context cx, Object o, TypeInfo target) { 26 | return switch (o) { 27 | case ItemPredicate i -> i; 28 | case ItemFilter filter -> ItemPredicate.Builder 29 | .item() 30 | .withSubPredicate(ItemFilterWrapper.TYPE, new ItemFilterWrapper(filter)) 31 | .build(); 32 | case Map map -> { 33 | RegistryAccessContainer registries = ((KubeJSContext) cx).getRegistries(); 34 | KubeOps ops = KubeOps.create(registries); 35 | yield ItemPredicate.CODEC.parse(ops, o).getOrThrow(); 36 | } 37 | default -> { 38 | var set = (HolderSet) cx.jsToJava(o, ITEM_HOLDER_SET); 39 | yield new ItemPredicate(Optional.of(set), 40 | MinMaxBounds.Ints.ANY, 41 | DataComponentPredicate.EMPTY, 42 | new HashMap<>()); 43 | } 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/LootEntryWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube.wrappers; 2 | 3 | import com.almostreliable.lootjs.core.entry.ItemLootEntry; 4 | import com.almostreliable.lootjs.core.entry.LootEntry; 5 | import dev.latvian.mods.kubejs.plugin.builtin.wrapper.ItemWrapper; 6 | import dev.latvian.mods.kubejs.script.ConsoleJS; 7 | import dev.latvian.mods.rhino.Context; 8 | import net.minecraft.world.item.ItemStack; 9 | 10 | import javax.annotation.Nullable; 11 | 12 | public class LootEntryWrapper { 13 | 14 | public static ItemLootEntry ofItemLootEntry(Context cx, @Nullable Object o) { 15 | if (o instanceof ItemLootEntry e) { 16 | return e; 17 | } 18 | 19 | ItemStack itemStack = ItemWrapper.wrap(cx, o); 20 | if (itemStack.isEmpty()) { 21 | ConsoleJS.SERVER.error("[LootEntry.of()] Invalid item stack, returning empty stack: " + o); 22 | ConsoleJS.SERVER.error("- Consider using `LootEntry.empty()` if you want to create an empty loot entry."); 23 | return LootEntry.of(ItemStack.EMPTY); 24 | } 25 | 26 | return LootEntry.of(itemStack); 27 | } 28 | 29 | public static LootEntry ofLootEntry(Context cx, @Nullable Object o) { 30 | if (o instanceof LootEntry entry) { 31 | return entry; 32 | } 33 | 34 | if (o instanceof String str && str.startsWith("#")) { 35 | String tag = str.substring(0, 1); 36 | return LootEntry.tag(tag, false); 37 | } 38 | 39 | return ofItemLootEntry(cx, o); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/MinMaxBoundsWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube.wrappers; 2 | 3 | import com.almostreliable.lootjs.LootJS; 4 | import dev.latvian.mods.kubejs.util.RegistryAccessContainer; 5 | import net.minecraft.advancements.critereon.MinMaxBounds; 6 | 7 | import javax.annotation.Nullable; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | 12 | public class MinMaxBoundsWrapper { 13 | 14 | private static Optional saveNumber(@Nullable Object o) { 15 | if (o instanceof Number n) { 16 | return Optional.of(n); 17 | } 18 | 19 | return Optional.empty(); 20 | } 21 | 22 | public static MinMaxBounds.Doubles ofMinMaxDoubles(RegistryAccessContainer registry, Object o) { 23 | if (o instanceof String s && s.equalsIgnoreCase("any")) { 24 | return MinMaxBounds.Doubles.ANY; 25 | } 26 | 27 | if (o instanceof List list) { 28 | if (list.size() == 1) { 29 | return ofMinMaxDoubles(registry, list.getFirst()); 30 | } 31 | 32 | if (list.size() == 2) { 33 | var min = saveNumber(list.get(0)).map(Number::doubleValue); 34 | var max = saveNumber(list.get(1)).map(Number::doubleValue); 35 | return new MinMaxBounds.Doubles(min, max, min.map(d -> d * d), max.map(d -> d * d)); 36 | } 37 | } 38 | 39 | if (o instanceof Number n) { 40 | return MinMaxBounds.Doubles.exactly(n.doubleValue()); 41 | } 42 | 43 | if (o instanceof MinMaxBounds minMaxBounds) { 44 | var min = minMaxBounds.min().map(Number::doubleValue); 45 | var max = minMaxBounds.max().map(Number::doubleValue); 46 | return new MinMaxBounds.Doubles(min, max, min.map(d -> d * d), max.map(d -> d * d)); 47 | } 48 | 49 | if (o instanceof Map) { 50 | return MinMaxBounds.Doubles.CODEC.parse(registry.java(), o).getOrThrow(); 51 | } 52 | 53 | LootJS.LOG.warn("Failed creating bounds, got: " + o); 54 | return MinMaxBounds.Doubles.exactly(Double.MAX_VALUE); 55 | } 56 | 57 | public static MinMaxBounds.Ints ofMinMaxInt(RegistryAccessContainer registry, Object o) { 58 | 59 | MinMaxBounds.Doubles doubles = ofMinMaxDoubles(registry, o); 60 | var min = doubles.min().map(Double::intValue); 61 | var max = doubles.max().map(Double::intValue); 62 | return new MinMaxBounds.Ints(min, max, min.map(i -> ((long) i * i)), max.map(i -> ((long) i * i))); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/MobEffectsPredicateWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube.wrappers; 2 | 3 | import com.almostreliable.lootjs.util.Utils; 4 | import dev.latvian.mods.kubejs.script.ConsoleJS; 5 | import dev.latvian.mods.kubejs.script.KubeJSContext; 6 | import dev.latvian.mods.rhino.Context; 7 | import dev.latvian.mods.rhino.type.RecordTypeInfo; 8 | import dev.latvian.mods.rhino.type.TypeInfo; 9 | import net.minecraft.advancements.critereon.MobEffectsPredicate; 10 | import net.minecraft.core.Holder; 11 | import net.minecraft.core.registries.Registries; 12 | import net.minecraft.resources.ResourceKey; 13 | import net.minecraft.resources.ResourceLocation; 14 | import net.minecraft.world.effect.MobEffect; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | public class MobEffectsPredicateWrapper { 20 | private static final RecordTypeInfo EIP_TYPE_INFO = (RecordTypeInfo) TypeInfo.of(MobEffectsPredicate.MobEffectInstancePredicate.class); 21 | 22 | public static MobEffectsPredicate of(Context cx, Object o) { 23 | if (o instanceof MobEffectsPredicate m) { 24 | return m; 25 | } 26 | 27 | var allEffects = ((KubeJSContext) cx).getRegistries().access().lookupOrThrow(Registries.MOB_EFFECT); 28 | Map, MobEffectsPredicate.MobEffectInstancePredicate> effectMap = new HashMap<>(); 29 | var list = Utils.listOrThrow(o); 30 | for (Object obj : list) { 31 | try { 32 | var map = Utils.mapOrThrow(obj); 33 | if (!map.containsKey("id")) { 34 | throw new RuntimeException("Expected effect id key"); 35 | } 36 | 37 | var effectId = map.get("id").toString(); 38 | var effect = allEffects.getOrThrow(ResourceKey.create(Registries.MOB_EFFECT, 39 | ResourceLocation.parse(effectId))); 40 | var instance = (MobEffectsPredicate.MobEffectInstancePredicate) EIP_TYPE_INFO.wrap(cx, 41 | map.get("duration"), 42 | EIP_TYPE_INFO); 43 | effectMap.put(effect, instance); 44 | } catch (Exception error) { 45 | ConsoleJS.SERVER.error(error); 46 | } 47 | } 48 | 49 | return new MobEffectsPredicate(effectMap); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/NumberProviderWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube.wrappers; 2 | 3 | import net.minecraft.world.level.storage.loot.providers.number.BinomialDistributionGenerator; 4 | import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; 5 | import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; 6 | import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; 7 | 8 | public class NumberProviderWrapper { 9 | 10 | public static NumberProvider constant(int value) { 11 | return new ConstantValue(value); 12 | } 13 | 14 | public static NumberProvider uniform(NumberProvider min, NumberProvider max) { 15 | return new UniformGenerator(min, max); 16 | } 17 | 18 | public static NumberProvider binomial(NumberProvider n, NumberProvider p) { 19 | return new BinomialDistributionGenerator(n, p); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/StatePropsPredicateWrapper.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.kube.wrappers; 2 | 3 | import com.almostreliable.lootjs.LootJS; 4 | import net.minecraft.advancements.critereon.StatePropertiesPredicate; 5 | 6 | import javax.annotation.Nullable; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import java.util.function.Consumer; 12 | 13 | public class StatePropsPredicateWrapper { 14 | 15 | public static StatePropertiesPredicate of(@Nullable Object o) { 16 | if (o instanceof StatePropertiesPredicate s) { 17 | return s; 18 | } 19 | 20 | if (!(o instanceof Map map)) { 21 | throw new IllegalArgumentException("Invalid object for state properties. Given type: " + 22 | (o == null ? "NULL" : o.getClass().getName())); 23 | } 24 | 25 | List props = new ArrayList<>(); 26 | loadMatchers(map, props::add); 27 | return new StatePropertiesPredicate(props); 28 | } 29 | 30 | private static void loadMatchers(Map map, Consumer onAdd) { 31 | for (Map.Entry entry : map.entrySet()) { 32 | if (!(entry.getKey() instanceof String propertyName)) { 33 | continue; 34 | } 35 | 36 | Object unknownValue = entry.getValue(); 37 | if (unknownValue instanceof Map minMaxValue) { 38 | var min = Optional.ofNullable(getSafePropertyValue(minMaxValue.get("min"))); 39 | var max = Optional.ofNullable(getSafePropertyValue(minMaxValue.get("max"))); 40 | 41 | var matcher = new StatePropertiesPredicate.RangedMatcher(min, max); 42 | onAdd.accept(new StatePropertiesPredicate.PropertyMatcher(propertyName, matcher)); 43 | continue; 44 | } 45 | 46 | String value = getSafePropertyValue(unknownValue); 47 | if (value == null) { 48 | LootJS.LOG.warn( 49 | "Can't validate value '" + unknownValue + "' for state properties. Skipping current property!"); 50 | continue; 51 | } 52 | 53 | var matcher = new StatePropertiesPredicate.ExactMatcher(value); 54 | onAdd.accept(new StatePropertiesPredicate.PropertyMatcher(propertyName, matcher)); 55 | } 56 | } 57 | 58 | @Nullable 59 | private static String getSafePropertyValue(@Nullable Object o) { 60 | if (o instanceof String s) { 61 | return s; 62 | } 63 | 64 | if (o instanceof Boolean b) { 65 | return Boolean.toString(b); 66 | } 67 | 68 | if (o instanceof Number n) { 69 | return Integer.toString(n.intValue()); 70 | } 71 | 72 | return null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/kube/wrappers/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.kube.wrappers; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/LootActionContainer.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot; 2 | 3 | import com.almostreliable.lootjs.core.entry.ItemLootEntry; 4 | import com.almostreliable.lootjs.core.entry.LootEntry; 5 | import com.almostreliable.lootjs.core.filters.ItemFilter; 6 | import com.almostreliable.lootjs.loot.modifier.GroupedLootAction; 7 | import com.almostreliable.lootjs.loot.modifier.LootAction; 8 | import com.almostreliable.lootjs.loot.modifier.handler.*; 9 | import com.almostreliable.lootjs.loot.table.MutableLootPool; 10 | import net.minecraft.core.component.DataComponentType; 11 | import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.level.Explosion; 13 | import net.minecraft.world.level.storage.loot.LootPool; 14 | import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; 15 | 16 | import java.util.function.Consumer; 17 | 18 | public interface LootActionContainer> { 19 | 20 | default A addLoot(LootEntry... entries) { 21 | return addAction(new AddLootAction(entries)); 22 | } 23 | 24 | default A addAlternativesLoot(LootEntry... entries) { 25 | return addAction(new AddLootAction(LootEntry.alternative(entries))); 26 | } 27 | 28 | default A addSequenceLoot(LootEntry... entries) { 29 | return addAction(new AddLootAction(LootEntry.sequence(entries))); 30 | } 31 | 32 | default A removeLoot(ItemFilter filter) { 33 | return addAction(new RemoveLootAction(filter)); 34 | } 35 | 36 | default A replaceLoot(ItemFilter filter, ItemLootEntry itemLootEntry) { 37 | return replaceLoot(filter, itemLootEntry, false); 38 | } 39 | 40 | default A replaceLoot(ItemFilter filter, ItemLootEntry itemLootEntry, boolean preserveCount) { 41 | return replaceLoot(filter, itemLootEntry, preserveCount, new DataComponentType[0]); 42 | } 43 | 44 | default A replaceLoot(ItemFilter filter, ItemLootEntry itemLootEntry, boolean preserveCount, DataComponentType[] preserveComponentTypes) { 45 | return addAction(new ReplaceLootAction(filter, itemLootEntry, preserveCount, preserveComponentTypes)); 46 | } 47 | 48 | default A modifyLoot(ItemFilter filter, ModifyLootAction.Callback callback) { 49 | return addAction(new ModifyLootAction(filter, callback)); 50 | } 51 | 52 | default A triggerExplosion(float radius, boolean destroy, boolean fire) { 53 | Explosion.BlockInteraction mode = destroy 54 | ? Explosion.BlockInteraction.DESTROY 55 | : Explosion.BlockInteraction.KEEP; 56 | return addAction(new ExplodeAction(radius, mode, fire)); 57 | } 58 | 59 | default A triggerExplosion(float radius, Explosion.BlockInteraction mode, boolean fire) { 60 | return addAction(new ExplodeAction(radius, mode, fire)); 61 | } 62 | 63 | default A triggerLightningStrike(boolean shouldDamage) { 64 | return addAction(new LightningStrikeAction(shouldDamage)); 65 | } 66 | 67 | default A dropExperience(NumberProvider amount) { 68 | return addAction(new DropExperienceAction(amount)); 69 | } 70 | 71 | default A customAction(LootAction action) { 72 | return addAction(action); 73 | } 74 | 75 | default A playerAction(Consumer action) { 76 | return addAction(new CustomPlayerAction(action)); 77 | } 78 | 79 | default A group(ItemFilter filter, Consumer onCreateGroup) { 80 | GroupedLootAction.Builder builder = new GroupedLootAction.Builder(filter); 81 | onCreateGroup.accept(builder); 82 | return addAction(builder.build()); 83 | } 84 | 85 | default A group(Consumer onCreateGroup) { 86 | GroupedLootAction.Builder builder = new GroupedLootAction.Builder(); 87 | onCreateGroup.accept(builder); 88 | return addAction(builder.build()); 89 | } 90 | 91 | default A pool(Consumer onCreatePool) { 92 | MutableLootPool pool = new MutableLootPool(LootPool.lootPool().build()); 93 | onCreatePool.accept(pool); 94 | return addAction(new LootPoolAction(pool.getVanillaPool())); 95 | } 96 | 97 | A addAction(LootAction action); 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/LootCondition.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot; 2 | 3 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 4 | 5 | public class LootCondition implements LootConditionsContainer { 6 | 7 | @Override 8 | public LootItemCondition addCondition(LootItemCondition condition) { 9 | return condition; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/LootConditionList.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot; 2 | 3 | import com.almostreliable.lootjs.core.filters.IdFilter; 4 | import com.almostreliable.lootjs.util.DebugInfo; 5 | import com.almostreliable.lootjs.util.ListHolder; 6 | import net.minecraft.core.registries.BuiltInRegistries; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.level.storage.loot.LootContext; 9 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 10 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 11 | 12 | import java.util.Iterator; 13 | import java.util.List; 14 | import java.util.function.Predicate; 15 | 16 | public class LootConditionList extends ListHolder 17 | implements LootConditionsContainer, Predicate { 18 | 19 | public LootConditionList() { 20 | super(); 21 | } 22 | 23 | public LootConditionList(List conditions) { 24 | super(conditions); 25 | } 26 | 27 | @Override 28 | public Iterator iterator() { 29 | return elements.listIterator(); 30 | } 31 | 32 | @Override 33 | protected LootItemCondition wrap(LootItemCondition entry) { 34 | return entry; 35 | } 36 | 37 | @Override 38 | protected LootItemCondition unwrap(LootItemCondition entry) { 39 | return entry; 40 | } 41 | 42 | @Override 43 | public LootConditionList addCondition(LootItemCondition condition) { 44 | this.add(condition); 45 | return this; 46 | } 47 | 48 | public void collectDebugInfo(DebugInfo info) { 49 | if (this.isEmpty()) return; 50 | 51 | info.add("% Conditions: ["); 52 | info.push(); 53 | for (var entry : this) { 54 | ResourceLocation key = BuiltInRegistries.LOOT_CONDITION_TYPE.getKey(entry.getType()); 55 | if (key == null) continue; 56 | info.add(key.toString()); 57 | } 58 | 59 | info.pop(); 60 | info.add("]"); 61 | } 62 | 63 | @Override 64 | public boolean test(LootContext context) { 65 | for (LootItemCondition condition : this) { 66 | if(!condition.test(context)) { 67 | return false; 68 | } 69 | } 70 | 71 | return true; 72 | } 73 | 74 | public boolean remove(IdFilter type) { 75 | return elements.removeIf(element -> type.test(BuiltInRegistries.LOOT_CONDITION_TYPE.getKey(element.getType()))); 76 | } 77 | 78 | public boolean contains(LootItemConditionType type) { 79 | return indexOf(type) != -1; 80 | } 81 | 82 | public int indexOf(LootItemConditionType type) { 83 | for (int i = 0; i < elements.size(); i++) { 84 | if (elements.get(i).getType().equals(type)) { 85 | return i; 86 | } 87 | } 88 | 89 | return -1; 90 | } 91 | 92 | public int lastIndexOf(LootItemConditionType type) { 93 | for (int i = elements.size() - 1; i >= 0; i--) { 94 | if (elements.get(i).getType().equals(type)) { 95 | return i; 96 | } 97 | } 98 | 99 | return -1; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/LootFunction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot; 2 | 3 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 4 | 5 | public record LootFunction() implements LootFunctionsContainer { 6 | 7 | @Override 8 | public LootItemFunction addFunction(LootItemFunction lootItemFunction) { 9 | return lootItemFunction; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/LootFunctionList.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot; 2 | 3 | import com.almostreliable.lootjs.core.filters.IdFilter; 4 | import com.almostreliable.lootjs.util.DebugInfo; 5 | import com.almostreliable.lootjs.util.ListHolder; 6 | import net.minecraft.core.registries.BuiltInRegistries; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.item.ItemStack; 9 | import net.minecraft.world.level.storage.loot.LootContext; 10 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 11 | import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; 12 | import org.apache.commons.lang3.mutable.MutableBoolean; 13 | 14 | import java.util.Iterator; 15 | import java.util.List; 16 | import java.util.function.BiFunction; 17 | 18 | public class LootFunctionList extends ListHolder 19 | implements LootFunctionsContainer, BiFunction { 20 | 21 | public LootFunctionList() { 22 | super(); 23 | } 24 | 25 | public LootFunctionList(List functions) { 26 | super(functions); 27 | } 28 | 29 | @Override 30 | public Iterator iterator() { 31 | return elements.listIterator(); 32 | } 33 | 34 | @Override 35 | protected LootItemFunction wrap(LootItemFunction entry) { 36 | return entry; 37 | } 38 | 39 | @Override 40 | protected LootItemFunction unwrap(LootItemFunction entry) { 41 | return entry; 42 | } 43 | 44 | @Override 45 | public LootFunctionList addFunction(LootItemFunction function) { 46 | this.add(function); 47 | return this; 48 | } 49 | 50 | public void collectDebugInfo(DebugInfo info) { 51 | if (this.isEmpty()) return; 52 | 53 | info.add("% Functions: ["); 54 | info.push(); 55 | for (var entry : this) { 56 | ResourceLocation key = BuiltInRegistries.LOOT_FUNCTION_TYPE.getKey(entry.getType()); 57 | if (key == null) continue; 58 | info.add(key.toString()); 59 | } 60 | 61 | info.pop(); 62 | info.add("]"); 63 | } 64 | 65 | @Override 66 | public ItemStack apply(ItemStack itemStack, LootContext context) { 67 | for (var entry : this) { 68 | itemStack = entry.apply(itemStack, context); 69 | } 70 | 71 | return itemStack; 72 | } 73 | 74 | public boolean replace(LootItemFunctionType type, LootItemFunction function) { 75 | MutableBoolean found = new MutableBoolean(false); 76 | elements.replaceAll(entry -> { 77 | if (entry.getType().equals(type)) { 78 | found.setValue(true); 79 | return function; 80 | } 81 | 82 | return entry; 83 | }); 84 | 85 | return found.booleanValue(); 86 | } 87 | 88 | public boolean remove(IdFilter type) { 89 | return elements.removeIf(element -> type.test(BuiltInRegistries.LOOT_FUNCTION_TYPE.getKey(element.getType()))); 90 | } 91 | 92 | public boolean contains(LootItemFunctionType type) { 93 | return indexOf(type) != -1; 94 | } 95 | 96 | public int indexOf(LootItemFunctionType type) { 97 | for (int i = 0; i < elements.size(); i++) { 98 | if (elements.get(i).getType().equals(type)) { 99 | return i; 100 | } 101 | } 102 | 103 | return -1; 104 | } 105 | 106 | public int lastIndexOf(LootItemFunctionType type) { 107 | for (int i = elements.size() - 1; i >= 0; i--) { 108 | if (elements.get(i).getType().equals(type)) { 109 | return i; 110 | } 111 | } 112 | 113 | return -1; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/Predicates.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot; 2 | 3 | import com.almostreliable.lootjs.core.filters.IdFilter; 4 | import net.minecraft.advancements.critereon.EnchantmentPredicate; 5 | import net.minecraft.advancements.critereon.ItemEnchantmentsPredicate; 6 | import net.minecraft.advancements.critereon.MinMaxBounds; 7 | import net.minecraft.advancements.critereon.NbtPredicate; 8 | import net.minecraft.core.Holder; 9 | import net.minecraft.core.HolderLookup; 10 | import net.minecraft.core.HolderSet; 11 | import net.minecraft.core.registries.Registries; 12 | import net.minecraft.nbt.CompoundTag; 13 | import net.minecraft.world.item.enchantment.Enchantment; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.Optional; 18 | 19 | public interface Predicates { 20 | 21 | public static HolderLookup.Provider lookup() { 22 | // TODO fix 23 | return null; 24 | } 25 | 26 | static EnchantmentPredicate enchantment(IdFilter filter) { 27 | return enchantment(filter, MinMaxBounds.Ints.ANY); 28 | } 29 | 30 | static EnchantmentPredicate enchantment(IdFilter filter, MinMaxBounds.Ints levelBound) { 31 | List> enchantments = lookup() 32 | .lookupOrThrow(Registries.ENCHANTMENT) 33 | .listElements() 34 | .filter(ref -> filter.test(ref.key().location())) 35 | .toList(); 36 | 37 | HolderSet.Direct holderSet = HolderSet.direct(enchantments); 38 | return new EnchantmentPredicate(Optional.of(holderSet), levelBound); 39 | } 40 | 41 | static ItemEnchantmentsPredicate itemEnchantments(EnchantmentPredicate[] predicates) { 42 | return ItemEnchantmentsPredicate.enchantments(Arrays.asList(predicates)); 43 | } 44 | 45 | static ItemEnchantmentsPredicate storedEnchantments(EnchantmentPredicate[] predicates) { 46 | return ItemEnchantmentsPredicate.storedEnchantments(Arrays.asList(predicates)); 47 | } 48 | 49 | static NbtPredicate nbt(CompoundTag nbt) { 50 | return new NbtPredicate(nbt); 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/CustomParamPredicate.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import net.minecraft.world.level.storage.loot.LootContext; 5 | import net.minecraft.world.level.storage.loot.parameters.LootContextParam; 6 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 7 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 8 | 9 | import java.util.Objects; 10 | import java.util.function.Predicate; 11 | 12 | public class CustomParamPredicate implements LootItemCondition { 13 | private final Predicate predicate; 14 | private final LootContextParam param; 15 | 16 | public CustomParamPredicate(LootContextParam param, Predicate predicate) { 17 | Objects.requireNonNull(param); 18 | Objects.requireNonNull(predicate); 19 | this.param = param; 20 | this.predicate = predicate; 21 | } 22 | 23 | @Override 24 | public boolean test(LootContext lootContext) { 25 | T paramOrNull = lootContext.getParamOrNull(param); 26 | return paramOrNull != null && predicate.test(paramOrNull); 27 | } 28 | 29 | 30 | @Override 31 | public LootItemConditionType getType() { 32 | return LootJSConditions.PARAM.value(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/IsLightLevel.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.world.level.storage.loot.LootContext; 6 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 7 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 8 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 9 | import net.minecraft.world.phys.Vec3; 10 | 11 | public record IsLightLevel(int min, int max) implements LootItemCondition { 12 | 13 | @Override 14 | public boolean test(LootContext context) { 15 | Vec3 origin = context.getParamOrNull(LootContextParams.ORIGIN); 16 | if (origin == null) { 17 | return false; 18 | } 19 | 20 | BlockPos blockPos = new BlockPos((int) origin.x, (int) origin.y, (int) origin.z); 21 | int light = context.getLevel().getMaxLocalRawBrightness(blockPos); 22 | return min <= light && light <= max; 23 | } 24 | 25 | 26 | @Override 27 | public LootItemConditionType getType() { 28 | return LootJSConditions.LIGHT_LEVEL.value(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/MatchAnyInventorySlot.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import com.almostreliable.lootjs.core.filters.ItemFilter; 5 | import com.almostreliable.lootjs.util.LootContextUtils; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.level.storage.loot.LootContext; 8 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 9 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 10 | 11 | public record MatchAnyInventorySlot(ItemFilter filter, boolean hotbar) implements LootItemCondition { 12 | 13 | @Override 14 | public boolean test(LootContext ctx) { 15 | ServerPlayer player = LootContextUtils.getPlayerOrNull(ctx); 16 | if (player == null) { 17 | return false; 18 | } 19 | 20 | var inv = player.getInventory(); 21 | int max = hotbar ? 9 : 36; 22 | for (int i = 0; i < max; i++) { 23 | if (filter.test(inv.getItem(i))) { 24 | return true; 25 | } 26 | } 27 | 28 | return false; 29 | } 30 | 31 | @Override 32 | public LootItemConditionType getType() { 33 | return LootJSConditions.MATCH_ANY_INVENTORY_SLOT.value(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/MatchBiome.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.Holder; 6 | import net.minecraft.core.HolderSet; 7 | import net.minecraft.world.level.biome.Biome; 8 | import net.minecraft.world.level.storage.loot.LootContext; 9 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 10 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 11 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 12 | import net.minecraft.world.phys.Vec3; 13 | 14 | public record MatchBiome(HolderSet biomes) implements LootItemCondition { 15 | 16 | @Override 17 | public boolean test(LootContext context) { 18 | Vec3 origin = context.getParamOrNull(LootContextParams.ORIGIN); 19 | if (origin == null) return false; 20 | 21 | BlockPos blockPos = new BlockPos((int) origin.x, (int) origin.y, (int) origin.z); 22 | Holder biome = context.getLevel().getBiome(blockPos); 23 | return biomes.contains(biome); 24 | } 25 | 26 | @Override 27 | public LootItemConditionType getType() { 28 | return LootJSConditions.BIOME.value(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/MatchDimension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import net.minecraft.resources.ResourceLocation; 5 | import net.minecraft.world.level.storage.loot.LootContext; 6 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 7 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 8 | 9 | public record MatchDimension(ResourceLocation[] dimensions) implements LootItemCondition { 10 | 11 | @Override 12 | public boolean test(LootContext context) { 13 | ResourceLocation levelDimension = context.getLevel().dimension().location(); 14 | for (ResourceLocation dimension : dimensions) { 15 | if (dimension.equals(levelDimension)) { 16 | return true; 17 | } 18 | } 19 | 20 | return false; 21 | } 22 | 23 | @Override 24 | public ResourceLocation[] dimensions() { 25 | return dimensions.clone(); 26 | } 27 | 28 | @Override 29 | public LootItemConditionType getType() { 30 | return LootJSConditions.ANY_DIMENSION.value(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/MatchEquipmentSlot.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import com.almostreliable.lootjs.core.filters.ItemFilter; 5 | import com.almostreliable.lootjs.util.LootContextUtils; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.entity.EquipmentSlot; 8 | import net.minecraft.world.level.storage.loot.LootContext; 9 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 10 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 11 | 12 | public record MatchEquipmentSlot(EquipmentSlot slot, ItemFilter itemFilter) implements LootItemCondition { 13 | 14 | @Override 15 | public boolean test(LootContext context) { 16 | ServerPlayer player = LootContextUtils.getPlayerOrNull(context); 17 | return player != null && itemFilter.test(player.getItemBySlot(slot)); 18 | } 19 | 20 | @Override 21 | public LootItemConditionType getType() { 22 | return LootJSConditions.MATCH_EQUIP.value(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/MatchKillerDistance.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import net.minecraft.advancements.critereon.DistancePredicate; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 8 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 9 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 10 | 11 | public record MatchKillerDistance(DistancePredicate predicate) implements LootItemCondition { 12 | 13 | @Override 14 | public boolean test(LootContext context) { 15 | Entity entity = context.getParamOrNull(LootContextParams.THIS_ENTITY); 16 | Entity killerEntity = context.getParamOrNull(LootContextParams.ATTACKING_ENTITY); 17 | return entity != null && killerEntity != null && predicate.matches(entity.getX(), 18 | entity.getY(), 19 | entity.getZ(), 20 | killerEntity.getX(), 21 | killerEntity.getY(), 22 | killerEntity.getZ()); 23 | 24 | } 25 | 26 | @Override 27 | public LootItemConditionType getType() { 28 | return LootJSConditions.DISTANCE.value(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/MatchPlayer.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import com.almostreliable.lootjs.util.LootContextUtils; 5 | import net.minecraft.advancements.critereon.EntityPredicate; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.level.storage.loot.LootContext; 8 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 9 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 10 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 11 | import net.minecraft.world.phys.Vec3; 12 | 13 | public record MatchPlayer(EntityPredicate predicate) implements LootItemCondition { 14 | 15 | @Override 16 | public boolean test(LootContext context) { 17 | ServerPlayer player = LootContextUtils.getPlayerOrNull(context); 18 | Vec3 origin = context.getParamOrNull(LootContextParams.ORIGIN); 19 | return predicate.matches(context.getLevel(), origin, player); 20 | } 21 | 22 | @Override 23 | public LootItemConditionType getType() { 24 | return LootJSConditions.MATCH_PLAYER.value(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/MatchStructure.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.HolderSet; 6 | import net.minecraft.world.level.levelgen.structure.Structure; 7 | import net.minecraft.world.level.storage.loot.LootContext; 8 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 9 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 10 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 11 | import net.minecraft.world.phys.Vec3; 12 | 13 | public record MatchStructure(HolderSet structures, boolean exact) implements LootItemCondition { 14 | 15 | @Override 16 | public boolean test(LootContext context) { 17 | Vec3 origin = context.getParamOrNull(LootContextParams.ORIGIN); 18 | if (origin == null) { 19 | return false; 20 | } 21 | 22 | var blockPos = new BlockPos((int) origin.x, (int) origin.y, (int) origin.z); 23 | var structureManager = context.getLevel().structureManager(); 24 | 25 | if (exact) { 26 | var start = structureManager.getStructureWithPieceAt(blockPos, structures); 27 | return start.isValid(); 28 | } 29 | 30 | for (var structure : structures) { 31 | var start = structureManager.getStructureAt(blockPos, structure.value()); 32 | if (start.isValid()) { 33 | return true; 34 | } 35 | } 36 | 37 | return false; 38 | } 39 | 40 | @Override 41 | public LootItemConditionType getType() { 42 | return LootJSConditions.ANY_STRUCTURE.value(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/PlayerParamPredicate.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition; 2 | 3 | import com.almostreliable.lootjs.LootJSConditions; 4 | import com.almostreliable.lootjs.util.LootContextUtils; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 8 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 9 | 10 | import java.util.Objects; 11 | import java.util.function.Predicate; 12 | 13 | public class PlayerParamPredicate implements LootItemCondition { 14 | private final Predicate predicate; 15 | 16 | public PlayerParamPredicate(Predicate predicate) { 17 | Objects.requireNonNull(predicate); 18 | this.predicate = predicate; 19 | } 20 | 21 | @Override 22 | public boolean test(LootContext lootContext) { 23 | ServerPlayer player = LootContextUtils.getPlayerOrNull(lootContext); 24 | return player != null && predicate.test(player); 25 | } 26 | 27 | 28 | @Override 29 | public LootItemConditionType getType() { 30 | return LootJSConditions.PLAYER_PARAM.value(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/builder/DistancePredicateBuilder.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.condition.builder; 2 | 3 | import net.minecraft.advancements.critereon.DistancePredicate; 4 | import net.minecraft.advancements.critereon.MinMaxBounds; 5 | 6 | public class DistancePredicateBuilder { 7 | private MinMaxBounds.Doubles x = MinMaxBounds.Doubles.ANY; 8 | private MinMaxBounds.Doubles y = MinMaxBounds.Doubles.ANY; 9 | private MinMaxBounds.Doubles z = MinMaxBounds.Doubles.ANY; 10 | private MinMaxBounds.Doubles horizontal = MinMaxBounds.Doubles.ANY; 11 | private MinMaxBounds.Doubles absolute = MinMaxBounds.Doubles.ANY; 12 | 13 | public DistancePredicateBuilder x(MinMaxBounds.Doubles bounds) { 14 | x = bounds; 15 | return this; 16 | } 17 | 18 | public DistancePredicateBuilder y(MinMaxBounds.Doubles bounds) { 19 | y = bounds; 20 | return this; 21 | } 22 | 23 | public DistancePredicateBuilder z(MinMaxBounds.Doubles bounds) { 24 | z = bounds; 25 | return this; 26 | } 27 | 28 | public DistancePredicateBuilder horizontal(MinMaxBounds.Doubles bounds) { 29 | horizontal = bounds; 30 | return this; 31 | } 32 | 33 | public DistancePredicateBuilder absolute(MinMaxBounds.Doubles bounds) { 34 | absolute = bounds; 35 | return this; 36 | } 37 | 38 | public DistancePredicate build() { 39 | return new DistancePredicate(x, y, z, horizontal, absolute); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/builder/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.loot.condition.builder; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/condition/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.loot.condition; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/extension/CompositeEntryBaseExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.extension; 2 | 3 | import dev.latvian.mods.rhino.util.HideFromJS; 4 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; 5 | 6 | import java.util.List; 7 | 8 | public interface CompositeEntryBaseExtension { 9 | List lootjs$getEntries(); 10 | 11 | @HideFromJS 12 | void lootjs$recompose(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/extension/LootContextExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.extension; 2 | 3 | import com.almostreliable.lootjs.LootJS; 4 | import com.almostreliable.lootjs.core.LootType; 5 | import com.almostreliable.lootjs.util.LootContextUtils; 6 | import dev.latvian.mods.rhino.util.RemapPrefixForJS; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.server.MinecraftServer; 9 | import net.minecraft.server.level.ServerPlayer; 10 | import net.minecraft.world.damagesource.DamageSource; 11 | import net.minecraft.world.entity.Entity; 12 | import net.minecraft.world.item.ItemStack; 13 | import net.minecraft.world.level.storage.loot.LootContext; 14 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 15 | import net.minecraft.world.phys.Vec3; 16 | 17 | import javax.annotation.Nullable; 18 | import java.util.Map; 19 | 20 | @RemapPrefixForJS("lootjs$") 21 | public interface LootContextExtension { 22 | 23 | static LootContextExtension cast(LootContext context) { 24 | return (LootContextExtension) context; 25 | } 26 | 27 | LootContext lootjs$self(); 28 | 29 | default ResourceLocation lootjs$getId() { 30 | return lootjs$self().getQueriedLootTableId(); 31 | } 32 | 33 | default boolean lootjs$isType(LootType type) { 34 | return type == lootjs$getType(); 35 | } 36 | 37 | LootType lootjs$getType(); 38 | 39 | default Vec3 lootjs$getPosition() { 40 | Vec3 pos = lootjs$self().getParamOrNull(LootContextParams.ORIGIN); 41 | if (pos != null) { 42 | return pos; 43 | } 44 | 45 | Entity entity = lootjs$getEntity(); 46 | if (entity != null) { 47 | return entity.position(); 48 | } 49 | 50 | LootJS.LOG.warn("Loot table {} has no position. This should not happen", lootjs$self().getQueriedLootTableId()); 51 | return Vec3.ZERO; 52 | } 53 | 54 | @Nullable 55 | default Entity lootjs$getEntity() { 56 | return lootjs$self().getParamOrNull(LootContextParams.THIS_ENTITY); 57 | } 58 | 59 | @Nullable 60 | default Entity lootjs$getAttackingEntity() { 61 | return lootjs$self().getParamOrNull(LootContextParams.ATTACKING_ENTITY); 62 | } 63 | 64 | @Nullable 65 | default ServerPlayer lootjs$getKillerPlayer() { 66 | return LootContextUtils.getPlayerOrNull(lootjs$self()); 67 | } 68 | 69 | @Nullable 70 | default DamageSource lootjs$getDamageSource() { 71 | return lootjs$self().getParamOrNull(LootContextParams.DAMAGE_SOURCE); 72 | } 73 | 74 | default ItemStack lootjs$getTool() { 75 | ItemStack tool = lootjs$self().getParamOrNull(LootContextParams.TOOL); 76 | if (tool != null) { 77 | return tool; 78 | } 79 | 80 | return ItemStack.EMPTY; 81 | } 82 | 83 | default boolean lootjs$isExploded() { 84 | return lootjs$self().hasParam(LootContextParams.EXPLOSION_RADIUS); 85 | } 86 | 87 | default float lootjs$getExplosionRadius() { 88 | Float f = lootjs$self().getParamOrNull(LootContextParams.EXPLOSION_RADIUS); 89 | return f != null ? f : 0f; 90 | } 91 | 92 | @Nullable 93 | default MinecraftServer lootjs$getServer() { 94 | return lootjs$self().getLevel().getServer(); 95 | } 96 | 97 | Map lootjs$getData(); 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/extension/LootItemFunctionExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.extension; 2 | 3 | import com.almostreliable.lootjs.loot.LootConditionList; 4 | import dev.latvian.mods.rhino.util.RemapPrefixForJS; 5 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 6 | 7 | import java.util.function.Consumer; 8 | 9 | @RemapPrefixForJS("lootjs$") 10 | public interface LootItemFunctionExtension { 11 | 12 | LootItemFunction lootjs$when(Consumer consumer); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/extension/LootParamsExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.extension; 2 | 3 | import com.almostreliable.lootjs.core.LootType; 4 | import dev.latvian.mods.rhino.util.RemapPrefixForJS; 5 | 6 | 7 | @RemapPrefixForJS("lootjs$") 8 | public interface LootParamsExtension { 9 | 10 | LootType lootjs$getType(); 11 | 12 | void lootjs$setType(LootType type); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/extension/LootPoolExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.extension; 2 | 3 | import com.almostreliable.lootjs.loot.LootConditionList; 4 | import com.almostreliable.lootjs.loot.LootEntryList; 5 | import com.almostreliable.lootjs.loot.LootFunctionList; 6 | import com.almostreliable.lootjs.util.DebugInfo; 7 | import com.mojang.serialization.JsonOps; 8 | import dev.latvian.mods.rhino.util.HideFromJS; 9 | import net.minecraft.world.level.storage.loot.LootPool; 10 | import net.minecraft.world.level.storage.loot.providers.number.NumberProviders; 11 | 12 | public interface LootPoolExtension { 13 | 14 | static LootPoolExtension cast(LootPool pool) { 15 | return (LootPoolExtension) pool; 16 | } 17 | 18 | LootPool lootjs$asVanillaPool(); 19 | 20 | LootEntryList lootjs$getEntries(); 21 | 22 | LootConditionList lootjs$getConditions(); 23 | 24 | LootFunctionList lootjs$getFunctions(); 25 | 26 | void lootjs$setName(String name); 27 | 28 | default void lootjs$collectDebugInfo(DebugInfo info) { 29 | var rollStr = NumberProviders.CODEC 30 | .encodeStart(JsonOps.INSTANCE, lootjs$asVanillaPool().getRolls()) 31 | .getOrThrow(); 32 | var bonusStr = NumberProviders.CODEC 33 | .encodeStart(JsonOps.INSTANCE, lootjs$asVanillaPool().getBonusRolls()) 34 | .getOrThrow(); 35 | info.add("% Rolls -> " + rollStr.toString()); 36 | info.add("% Bonus rolls -> " + bonusStr.toString()); 37 | lootjs$getEntries().collectDebugInfo(info); 38 | lootjs$getConditions().collectDebugInfo(info); 39 | lootjs$getFunctions().collectDebugInfo(info); 40 | } 41 | 42 | @HideFromJS 43 | void lootjs$recompose(); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/extension/LootTableExtension.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.extension; 2 | 3 | import com.almostreliable.lootjs.loot.LootFunctionList; 4 | import dev.latvian.mods.rhino.util.HideFromJS; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.world.level.storage.loot.LootPool; 7 | import net.minecraft.world.level.storage.loot.LootTable; 8 | import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet; 9 | 10 | import javax.annotation.Nullable; 11 | import java.util.List; 12 | 13 | public interface LootTableExtension { 14 | 15 | static LootTableExtension cast(LootTable table) { 16 | return (LootTableExtension) table; 17 | } 18 | 19 | List lootjs$getPools(); 20 | 21 | void lootjs$setPools(List pools); 22 | 23 | LootFunctionList lootjs$createFunctionList(); 24 | 25 | void lootjs$setRandomSequence(@Nullable ResourceLocation randomSequence); 26 | 27 | @Nullable 28 | ResourceLocation lootjs$getRandomSequence(); 29 | 30 | void lootjs$setParamSet(LootContextParamSet paramSet); 31 | 32 | LootContextParamSet lootjs$getParamSet(); 33 | 34 | @HideFromJS 35 | void lootjs$recompose(); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/extension/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.loot.extension; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/LootAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import net.minecraft.world.level.storage.loot.LootContext; 5 | 6 | @FunctionalInterface 7 | public interface LootAction { 8 | void apply(LootContext context, LootBucket loot); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/AddLootAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.core.entry.LootEntry; 5 | import com.almostreliable.lootjs.loot.modifier.LootAction; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; 8 | 9 | public class AddLootAction implements LootAction { 10 | 11 | private final LootPoolEntryContainer[] entries; 12 | 13 | public AddLootAction(LootEntry... entries) { 14 | this.entries = new LootPoolEntryContainer[entries.length]; 15 | for (int i = 0; i < entries.length; i++) { 16 | this.entries[i] = entries[i].getVanillaEntry(); 17 | } 18 | } 19 | 20 | public LootPoolEntryContainer[] entries() { 21 | return entries.clone(); 22 | } 23 | 24 | @Override 25 | public void apply(LootContext context, LootBucket loot) { 26 | for (var entry : entries) { 27 | entry.expand(context, lootPoolEntry -> { 28 | lootPoolEntry.createItemStack(loot::addItem, context); 29 | }); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/CustomPlayerAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.loot.modifier.LootAction; 5 | import com.almostreliable.lootjs.util.LootContextUtils; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.level.storage.loot.LootContext; 8 | 9 | import java.util.Objects; 10 | import java.util.function.Consumer; 11 | 12 | public class CustomPlayerAction implements LootAction { 13 | 14 | private final Consumer action; 15 | 16 | public CustomPlayerAction(Consumer action) { 17 | Objects.requireNonNull(action); 18 | this.action = action; 19 | } 20 | 21 | @Override 22 | public void apply(LootContext context, LootBucket loot) { 23 | ServerPlayer playerOrNull = LootContextUtils.getPlayerOrNull(context); 24 | if (playerOrNull != null) { 25 | action.accept(playerOrNull); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/DropExperienceAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.loot.modifier.LootAction; 5 | import net.minecraft.world.entity.ExperienceOrb; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 8 | import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; 9 | import net.minecraft.world.phys.Vec3; 10 | 11 | public class DropExperienceAction implements LootAction { 12 | private final NumberProvider amount; 13 | 14 | public DropExperienceAction(NumberProvider amount) { 15 | this.amount = amount; 16 | } 17 | 18 | @Override 19 | public void apply(LootContext context, LootBucket loot) { 20 | Vec3 origin = context.getParamOrNull(LootContextParams.ORIGIN); 21 | if (origin != null) { 22 | ExperienceOrb.award(context.getLevel(), origin, amount.getInt(context)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/ExplodeAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.loot.modifier.LootAction; 5 | import net.minecraft.world.level.Explosion; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 8 | import net.minecraft.world.phys.Vec3; 9 | 10 | public class ExplodeAction implements LootAction { 11 | 12 | private final float radius; 13 | private final boolean fire; 14 | private final Explosion.BlockInteraction mode; 15 | 16 | public ExplodeAction(float radius, Explosion.BlockInteraction mode, boolean fire) { 17 | this.radius = radius; 18 | this.fire = fire; 19 | this.mode = mode; 20 | } 21 | 22 | @Override 23 | public void apply(LootContext context, LootBucket loot) { 24 | Vec3 origin = context.getParamOrNull(LootContextParams.ORIGIN); 25 | // TODO Explosion 26 | // Explosion explosion = new Explosion(context.getLevel(), 27 | // null, 28 | // null, 29 | // null, 30 | // origin.x, 31 | // origin.y, 32 | // origin.z, 33 | // radius, 34 | // fire, 35 | // mode); 36 | // explosion.explode(); 37 | // explosion.finalizeExplosion(true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/LightningStrikeAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.loot.modifier.LootAction; 5 | import net.minecraft.world.entity.EntityType; 6 | import net.minecraft.world.entity.LightningBolt; 7 | import net.minecraft.world.level.storage.loot.LootContext; 8 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 9 | import net.minecraft.world.phys.Vec3; 10 | 11 | public class LightningStrikeAction implements LootAction { 12 | private final boolean shouldDamageEntity; 13 | 14 | public LightningStrikeAction(boolean shouldDamageEntity) { 15 | this.shouldDamageEntity = shouldDamageEntity; 16 | } 17 | 18 | @Override 19 | public void apply(LootContext context, LootBucket loot) { 20 | Vec3 origin = context.getParamOrNull(LootContextParams.ORIGIN); 21 | if (origin != null) { 22 | LightningBolt lightning = EntityType.LIGHTNING_BOLT.create(context.getLevel()); 23 | if (lightning != null) { 24 | lightning.moveTo(origin.x, origin.y, origin.z); 25 | if (!shouldDamageEntity) lightning.setVisualOnly(true); 26 | context.getLevel().addFreshEntity(lightning); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/LootPoolAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.loot.modifier.LootAction; 5 | import net.minecraft.world.level.storage.loot.LootContext; 6 | import net.minecraft.world.level.storage.loot.LootPool; 7 | 8 | public record LootPoolAction(LootPool pool) implements LootAction { 9 | @Override 10 | public void apply(LootContext context, LootBucket loot) { 11 | pool.addRandomItems(loot::addItem, context); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/ModifyLootAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.core.filters.ItemFilter; 5 | import com.almostreliable.lootjs.loot.modifier.LootAction; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.level.storage.loot.LootContext; 8 | 9 | public record ModifyLootAction(ItemFilter predicate, 10 | ModifyLootAction.Callback callback) 11 | implements LootAction { 12 | 13 | public void apply(LootContext context, LootBucket loot) { 14 | loot.modifyItems(itemStack -> { 15 | if (predicate.test(itemStack)) { 16 | return callback.modify(itemStack); 17 | } 18 | 19 | return itemStack; 20 | }); 21 | } 22 | 23 | @FunctionalInterface 24 | public interface Callback { 25 | ItemStack modify(ItemStack itemStack); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/RemoveLootAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.core.filters.ItemFilter; 5 | import com.almostreliable.lootjs.loot.modifier.LootAction; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | 8 | public record RemoveLootAction(ItemFilter filter) implements LootAction { 9 | 10 | @Override 11 | public void apply(LootContext context, LootBucket loot) { 12 | loot.remove(filter); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/ReplaceLootAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.modifier.handler; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import com.almostreliable.lootjs.core.entry.ItemLootEntry; 5 | import com.almostreliable.lootjs.core.filters.ItemFilter; 6 | import com.almostreliable.lootjs.loot.modifier.LootAction; 7 | import net.minecraft.core.component.DataComponentType; 8 | import net.minecraft.world.level.storage.loot.LootContext; 9 | 10 | public record ReplaceLootAction(ItemFilter filter, ItemLootEntry itemLootEntry, boolean preserveCount, 11 | DataComponentType[] preserveComponentTypes) 12 | implements LootAction { 13 | 14 | @Override 15 | public void apply(LootContext context, LootBucket loot) { 16 | loot.replace(filter, itemLootEntry, preserveCount, preserveComponentTypes); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/handler/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.loot.modifier.handler; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/modifier/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.loot.modifier; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.loot; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/LootEntriesTransformer.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.table; 2 | 3 | import com.almostreliable.lootjs.core.entry.ItemLootEntry; 4 | import com.almostreliable.lootjs.core.entry.SimpleLootEntry; 5 | import com.almostreliable.lootjs.core.entry.TableReferenceLootEntry; 6 | import com.almostreliable.lootjs.core.entry.TagLootEntry; 7 | import com.almostreliable.lootjs.core.filters.IdFilter; 8 | import com.almostreliable.lootjs.core.filters.ItemFilter; 9 | import net.minecraft.world.item.Item; 10 | 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | import java.util.function.Predicate; 13 | import java.util.function.UnaryOperator; 14 | 15 | @SuppressWarnings("UnusedReturnValue") 16 | public interface LootEntriesTransformer { 17 | 18 | LootEntriesTransformer modifyEntry(UnaryOperator onTransform, boolean deepTransform); 19 | 20 | default LootEntriesTransformer modifyEntry(UnaryOperator onTransform) { 21 | modifyEntry(onTransform, true); 22 | return this; 23 | } 24 | 25 | default LootEntriesTransformer modifyItemEntry(UnaryOperator onTransform) { 26 | modifyEntry(le -> { 27 | if (le instanceof ItemLootEntry ile) { 28 | return onTransform.apply(ile); 29 | } 30 | 31 | return le; 32 | }); 33 | 34 | return this; 35 | } 36 | 37 | LootEntriesTransformer removeEntry(Predicate onRemove, boolean deepRemove); 38 | 39 | default LootEntriesTransformer removeEntry(Predicate onRemove) { 40 | removeEntry(onRemove, true); 41 | return this; 42 | } 43 | 44 | default LootEntriesTransformer removeItem(ItemFilter filter) { 45 | removeItem(filter, true); 46 | return this; 47 | } 48 | 49 | default LootEntriesTransformer removeItem(ItemFilter filter, boolean deepRemove) { 50 | removeEntry(entry -> entry instanceof ItemLootEntry ile && ile.test(filter), deepRemove); 51 | return this; 52 | } 53 | 54 | default LootEntriesTransformer removeTag(String tag) { 55 | removeTag(tag, true); 56 | return this; 57 | } 58 | 59 | default LootEntriesTransformer removeTag(String tag, boolean deepRemove) { 60 | removeEntry(entry -> entry instanceof TagLootEntry tle && tle.isTag(tag), deepRemove); 61 | return this; 62 | } 63 | 64 | default LootEntriesTransformer removeReference(IdFilter filter) { 65 | removeReference(filter, true); 66 | return this; 67 | } 68 | 69 | default LootEntriesTransformer removeReference(IdFilter filter, boolean deepRemove) { 70 | removeEntry(entry -> entry instanceof TableReferenceLootEntry d && filter.test(d.getLocation()), deepRemove); 71 | return this; 72 | } 73 | 74 | default LootEntriesTransformer replaceItem(ItemFilter filter, Item item) { 75 | replaceItem(filter, item, true); 76 | return this; 77 | } 78 | 79 | default LootEntriesTransformer replaceItem(ItemFilter filter, Item item, boolean deepReplace) { 80 | modifyEntry(entry -> { 81 | if (entry instanceof ItemLootEntry ile && ile.test(filter)) { 82 | ile.setItem(item); 83 | } 84 | 85 | return entry; 86 | }, deepReplace); 87 | return this; 88 | } 89 | 90 | default boolean hasItem(ItemFilter filter) { 91 | AtomicBoolean hasItem = new AtomicBoolean(false); 92 | modifyItemEntry(ile -> { 93 | if (hasItem.get() || ile.test(filter)) { 94 | hasItem.set(true); 95 | } 96 | 97 | return ile; 98 | }); 99 | 100 | return hasItem.get(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/LootEntryAppender.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.table; 2 | 3 | import com.almostreliable.lootjs.core.entry.LootEntry; 4 | import com.google.gson.JsonObject; 5 | import com.mojang.serialization.JsonOps; 6 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntries; 7 | 8 | public interface LootEntryAppender { 9 | default LootEntryAppender addCustomEntry(JsonObject json) { 10 | var vanilla = LootPoolEntries.CODEC.parse(JsonOps.INSTANCE, json).getOrThrow(); 11 | return addEntry(LootEntry.ofVanilla(vanilla)); 12 | } 13 | 14 | LootEntryAppender addEntry(LootEntry entry); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/LootTableList.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.table; 2 | 3 | import com.almostreliable.lootjs.core.entry.SimpleLootEntry; 4 | import com.almostreliable.lootjs.loot.LootFunctionList; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.function.Consumer; 10 | import java.util.function.Predicate; 11 | import java.util.function.UnaryOperator; 12 | 13 | public class LootTableList implements LootEntriesTransformer, Iterable { 14 | 15 | private final List tables; 16 | 17 | public LootTableList(List tables) { 18 | this.tables = tables; 19 | } 20 | 21 | public List getTables() { 22 | return tables; 23 | } 24 | 25 | public LootTableList createPool(Consumer onCreatePool) { 26 | getTables().forEach(table -> table.createPool(onCreatePool)); 27 | return this; 28 | } 29 | 30 | public LootTableList firstPool(Consumer onModifyPool) { 31 | getTables().forEach(table -> table.firstPool(onModifyPool)); 32 | return this; 33 | } 34 | 35 | public LootTableList onDrop(PostLootAction postLootAction) { 36 | getTables().forEach(table -> table.onDrop(postLootAction)); 37 | return this; 38 | } 39 | 40 | public LootTableList clear() { 41 | getTables().forEach(MutableLootTable::clear); 42 | return this; 43 | } 44 | 45 | public LootTableList apply(Consumer onModifiers) { 46 | getTables().forEach(table -> table.apply(onModifiers)); 47 | return this; 48 | } 49 | 50 | @Override 51 | public LootTableList modifyEntry(UnaryOperator onTransform, boolean deepTransform) { 52 | getTables().forEach(table -> table.modifyEntry(onTransform, deepTransform)); 53 | return this; 54 | } 55 | 56 | @Override 57 | public LootTableList removeEntry(Predicate onRemove, boolean deepRemove) { 58 | getTables().forEach(table -> table.removeEntry(onRemove, deepRemove)); 59 | return this; 60 | } 61 | 62 | @NotNull 63 | @Override 64 | public Iterator iterator() { 65 | return getTables().iterator(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/LootTracker.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.table; 2 | 3 | import net.minecraft.resources.ResourceLocation; 4 | import net.minecraft.world.item.ItemStack; 5 | 6 | import javax.annotation.Nullable; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.function.Consumer; 10 | 11 | public class LootTracker implements Consumer { 12 | 13 | private final List loot = new ArrayList<>(); 14 | private final Consumer originConsumer; 15 | @Nullable 16 | private final ResourceLocation tableId; 17 | 18 | public LootTracker(Consumer originConsumer, @Nullable ResourceLocation tableId) { 19 | this.originConsumer = originConsumer; 20 | this.tableId = tableId; 21 | } 22 | 23 | @Override 24 | public void accept(ItemStack itemStack) { 25 | loot.add(itemStack); 26 | } 27 | 28 | public List getLoot() { 29 | return loot; 30 | } 31 | 32 | public void resolve() { 33 | loot.forEach(originConsumer); 34 | } 35 | 36 | @Nullable 37 | public ResourceLocation getTableId() { 38 | return tableId; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "LootTracker[" + tableId + "] " + loot.size() + "x items"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/MutableLootPool.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.table; 2 | 3 | import com.almostreliable.lootjs.core.entry.LootEntry; 4 | import com.almostreliable.lootjs.core.entry.SimpleLootEntry; 5 | import com.almostreliable.lootjs.loot.LootConditionList; 6 | import com.almostreliable.lootjs.loot.LootEntryList; 7 | import com.almostreliable.lootjs.loot.LootFunctionList; 8 | import com.almostreliable.lootjs.loot.extension.LootPoolExtension; 9 | import net.minecraft.world.level.storage.loot.LootPool; 10 | import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; 11 | 12 | import javax.annotation.Nullable; 13 | import java.util.function.Consumer; 14 | import java.util.function.Predicate; 15 | import java.util.function.UnaryOperator; 16 | 17 | public class MutableLootPool implements LootEntriesTransformer, LootEntryAppender { 18 | 19 | @Nullable private LootConditionList conditions; 20 | @Nullable private LootFunctionList functions; 21 | @Nullable private LootEntryList entries; 22 | private final LootPool vanillaPool; 23 | 24 | public MutableLootPool(LootPool pool) { 25 | vanillaPool = pool; 26 | } 27 | 28 | @Nullable 29 | public String getName() { 30 | return vanillaPool.getName(); 31 | } 32 | 33 | public MutableLootPool name(String name) { 34 | LootPoolExtension.cast(vanillaPool).lootjs$setName(name); 35 | return this; 36 | } 37 | 38 | public MutableLootPool rolls(NumberProvider rolls) { 39 | vanillaPool.setRolls(rolls); 40 | return this; 41 | } 42 | 43 | public MutableLootPool bonusRolls(NumberProvider bonusRolls) { 44 | vanillaPool.setBonusRolls(bonusRolls); 45 | return this; 46 | } 47 | 48 | public MutableLootPool when(Consumer onConditions) { 49 | onConditions.accept(getConditions()); 50 | return this; 51 | } 52 | 53 | public MutableLootPool apply(Consumer onModifiers) { 54 | onModifiers.accept(getFunctions()); 55 | return this; 56 | } 57 | 58 | public LootConditionList getConditions() { 59 | if (conditions == null) { 60 | conditions = LootPoolExtension.cast(vanillaPool).lootjs$getConditions(); 61 | } 62 | 63 | return conditions; 64 | } 65 | 66 | public LootFunctionList getFunctions() { 67 | if (functions == null) { 68 | functions = LootPoolExtension.cast(vanillaPool).lootjs$getFunctions(); 69 | } 70 | 71 | return functions; 72 | } 73 | 74 | public LootEntryList getEntries() { 75 | if (entries == null) { 76 | entries = LootPoolExtension.cast(vanillaPool).lootjs$getEntries(); 77 | } 78 | 79 | return entries; 80 | } 81 | 82 | public LootPool getVanillaPool() { 83 | return vanillaPool; 84 | } 85 | 86 | @Override 87 | public MutableLootPool addEntry(LootEntry entry) { 88 | getEntries().add(entry); 89 | return this; 90 | } 91 | 92 | @Override 93 | public MutableLootPool modifyEntry(UnaryOperator onTransform, boolean deepTransform) { 94 | getEntries().modifyEntry(onTransform, deepTransform); 95 | return this; 96 | } 97 | 98 | @Override 99 | public MutableLootPool removeEntry(Predicate onRemove, boolean deepRemove) { 100 | getEntries().removeEntry(onRemove, deepRemove); 101 | return this; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/PostLootAction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.table; 2 | 3 | import com.almostreliable.lootjs.core.LootBucket; 4 | import net.minecraft.world.level.storage.loot.LootContext; 5 | 6 | @FunctionalInterface 7 | public interface PostLootAction { 8 | 9 | void alter(LootContext context, LootBucket loot); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/PostLootActionOwner.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.loot.table; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | public interface PostLootActionOwner { 6 | 7 | void lootjs$setPostLootAction(PostLootAction postLootAction); 8 | 9 | @Nullable 10 | PostLootAction lootjs$getPostLootAction(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/loot/table/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.loot.table; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/CompositeEntryBaseMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.loot.extension.CompositeEntryBaseExtension; 4 | import dev.latvian.mods.rhino.util.HideFromJS; 5 | import net.minecraft.world.level.storage.loot.entries.ComposableEntryContainer; 6 | import net.minecraft.world.level.storage.loot.entries.CompositeEntryBase; 7 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Mutable; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @Mixin(CompositeEntryBase.class) 17 | public abstract class CompositeEntryBaseMixin implements CompositeEntryBaseExtension { 18 | 19 | @Mutable @Final @Shadow public List children; 20 | 21 | @Mutable @Shadow @Final private ComposableEntryContainer composedChildren; 22 | 23 | 24 | @Shadow 25 | protected abstract ComposableEntryContainer compose(List list); 26 | 27 | @Override 28 | public List lootjs$getEntries() { 29 | if (this.children.getClass() != ArrayList.class) { // We only want exactly ArrayList, so we know its mutable 30 | this.children = new ArrayList<>(this.children); 31 | } 32 | 33 | return this.children; 34 | } 35 | 36 | @HideFromJS 37 | @Override 38 | public void lootjs$recompose() { 39 | if (this.children.getClass() != ArrayList.class) { 40 | return; 41 | } 42 | 43 | this.composedChildren = this.compose(this.children); 44 | for (var child : children) { 45 | if (child instanceof CompositeEntryBaseExtension ext) { 46 | ext.lootjs$recompose(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/CreeperMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.LootModificationsAPI; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.world.damagesource.DamageSource; 6 | import net.minecraft.world.entity.monster.Creeper; 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.CallbackInfo; 11 | 12 | @Mixin(Creeper.class) 13 | public class CreeperMixin { 14 | @Inject(method = "dropCustomDeathLoot", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/monster/Creeper;increaseDroppedSkulls()V"), cancellable = true) 15 | private void modifyWitherBossStarDrop(ServerLevel arg, DamageSource arg2, boolean bl, CallbackInfo ci) { 16 | if (LootModificationsAPI.DISABLE_CREEPER_DROPPING_HEAD) { 17 | ci.cancel(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/EntityPredicateMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import dev.latvian.mods.rhino.util.RemapForJS; 4 | import net.minecraft.advancements.critereon.DistancePredicate; 5 | import net.minecraft.advancements.critereon.EntityPredicate; 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.Optional; 11 | 12 | @SuppressWarnings({ "unused", "OptionalUsedAsFieldOrParameterType" }) 13 | @Mixin(EntityPredicate.class) 14 | public abstract class EntityPredicateMixin { 15 | @RemapForJS("distance") 16 | @Shadow 17 | @Final 18 | private Optional distanceToPlayer; 19 | 20 | @RemapForJS("distance") 21 | @Shadow 22 | public abstract Optional distanceToPlayer(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/HolderSetCodecMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.kube.KubeOps; 4 | import com.mojang.datafixers.util.Pair; 5 | import com.mojang.serialization.DataResult; 6 | import com.mojang.serialization.DynamicOps; 7 | import dev.latvian.mods.kubejs.holder.NamespaceHolderSet; 8 | import dev.latvian.mods.kubejs.holder.RegExHolderSet; 9 | import dev.latvian.mods.kubejs.util.RegExpKJS; 10 | import net.minecraft.core.HolderLookup; 11 | import net.minecraft.core.HolderSet; 12 | import net.minecraft.core.Registry; 13 | import net.minecraft.resources.HolderSetCodec; 14 | import net.minecraft.resources.ResourceKey; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Shadow; 18 | import org.spongepowered.asm.mixin.Unique; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 22 | 23 | import javax.annotation.Nullable; 24 | import java.util.regex.Pattern; 25 | 26 | @Mixin(HolderSetCodec.class) 27 | public class HolderSetCodecMixin { 28 | 29 | @Shadow @Final private ResourceKey> registryKey; 30 | 31 | @Inject(method = "decode", at = @At(value = "INVOKE", target = "Ljava/util/Optional;get()Ljava/lang/Object;"), cancellable = true) 32 | private void lootjs$injectHolderCodec(DynamicOps dynamicOps, T value, CallbackInfoReturnable> cir) { 33 | if (!(dynamicOps instanceof KubeOps kubeOps)) { 34 | return; 35 | } 36 | 37 | var namespaceSet = lootjs$tryByNamespace(kubeOps, value); 38 | if (namespaceSet != null) { 39 | var result = DataResult.success(Pair.of(namespaceSet, value)); 40 | cir.setReturnValue(result); 41 | return; 42 | } 43 | 44 | var regexSet = lootjs$tryByRegex(kubeOps, value); 45 | if (regexSet != null) { 46 | var result = DataResult.success(Pair.of(regexSet, value)); 47 | cir.setReturnValue(result); 48 | } 49 | } 50 | 51 | @Unique 52 | @Nullable 53 | private HolderSet lootjs$tryByNamespace(KubeOps kubeOps, T value) { 54 | if (!(value instanceof String str && str.startsWith("@"))) { 55 | return null; 56 | } 57 | 58 | if (!(kubeOps.getter(this.registryKey).orElse(null) instanceof HolderLookup.RegistryLookup lookup)) { 59 | return null; 60 | } 61 | 62 | var namespace = str.substring(1); 63 | return new NamespaceHolderSet<>(lookup, namespace); 64 | } 65 | 66 | @Unique 67 | @Nullable 68 | private HolderSet lootjs$tryByRegex(KubeOps kubeOps, T value) { 69 | Pattern pattern = RegExpKJS.wrap(value); 70 | if (pattern == null) { 71 | return null; 72 | } 73 | 74 | if (!(kubeOps.getter(this.registryKey).orElse(null) instanceof HolderLookup.RegistryLookup lookup)) { 75 | return null; 76 | } 77 | 78 | return new RegExHolderSet<>(lookup, pattern); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/ItemPredicateMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.core.filters.ItemFilter; 4 | import net.minecraft.advancements.critereon.ItemPredicate; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | 7 | @Mixin(ItemPredicate.class) 8 | public abstract class ItemPredicateMixin implements ItemFilter { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/LootContextMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.core.LootType; 4 | import com.almostreliable.lootjs.loot.extension.LootContextExtension; 5 | import com.almostreliable.lootjs.loot.extension.LootParamsExtension; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | import net.minecraft.world.level.storage.loot.LootParams; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.Unique; 12 | 13 | import javax.annotation.Nullable; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @Mixin(LootContext.class) 18 | public class LootContextMixin implements LootContextExtension { 19 | 20 | @Shadow @Final private LootParams params; 21 | @Unique 22 | @Nullable 23 | private Map lootjs$data; 24 | 25 | @Override 26 | public LootContext lootjs$self() { 27 | return (LootContext) (Object) this; 28 | } 29 | 30 | @Override 31 | public Map lootjs$getData() { 32 | if (lootjs$data == null) { 33 | lootjs$data = new HashMap<>(); 34 | } 35 | 36 | return lootjs$data; 37 | } 38 | 39 | @Override 40 | public LootType lootjs$getType() { 41 | return ((LootParamsExtension) this.params).lootjs$getType(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/LootItemConditionMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import dev.latvian.mods.rhino.util.RemapPrefixForJS; 4 | import net.minecraft.world.level.storage.loot.predicates.AllOfCondition; 5 | import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; 6 | import net.minecraft.world.level.storage.loot.predicates.InvertedLootItemCondition; 7 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | 14 | @Mixin(LootItemCondition.class) 15 | @RemapPrefixForJS("lootjs$") 16 | public interface LootItemConditionMixin { 17 | 18 | 19 | @Unique 20 | default LootItemCondition lootjs$not() { 21 | return new InvertedLootItemCondition((LootItemCondition) this); 22 | } 23 | 24 | @Unique 25 | default LootItemCondition lootjs$or(LootItemCondition... other) { 26 | ArrayList l = new ArrayList<>(); 27 | l.add((LootItemCondition) this); 28 | Collections.addAll(l, other); 29 | return new AnyOfCondition(l); 30 | } 31 | 32 | @Unique 33 | default LootItemCondition lootjs$and(LootItemCondition... other) { 34 | ArrayList l = new ArrayList<>(); 35 | l.add((LootItemCondition) this); 36 | Collections.addAll(l, other); 37 | 38 | return new AllOfCondition(l); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/LootItemConditionalFunctionMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.loot.LootConditionList; 4 | import com.almostreliable.lootjs.loot.extension.LootItemFunctionExtension; 5 | import net.minecraft.world.level.storage.loot.LootContext; 6 | import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction; 7 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 8 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Mutable; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | 14 | import java.util.List; 15 | import java.util.function.Consumer; 16 | import java.util.function.Predicate; 17 | 18 | @Mixin(LootItemConditionalFunction.class) 19 | public class LootItemConditionalFunctionMixin implements LootItemFunctionExtension { 20 | @Mutable @Shadow @Final protected List predicates; 21 | @Mutable @Shadow @Final private Predicate compositePredicates; 22 | 23 | @Override 24 | public LootItemFunction lootjs$when(Consumer consumer) { 25 | var conditions = new LootConditionList(this.predicates); 26 | consumer.accept(conditions); 27 | this.predicates = conditions.getElements(); 28 | this.compositePredicates = conditions; 29 | return (LootItemFunction) this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/LootItemFunctionMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.LootJS; 4 | import com.almostreliable.lootjs.loot.LootConditionList; 5 | import com.almostreliable.lootjs.loot.extension.LootItemFunctionExtension; 6 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | 9 | import java.util.function.Consumer; 10 | 11 | @Mixin(LootItemFunction.class) 12 | public interface LootItemFunctionMixin extends LootItemFunctionExtension { 13 | 14 | @Override 15 | default LootItemFunction lootjs$when(Consumer consumer) { 16 | LootJS.DEBUG_ACTION.accept( 17 | "Non conditional loot functions are not supported yet! Added conditions will be ignored!"); 18 | return (LootItemFunction) this; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/LootParamsBuilderMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.core.LootType; 4 | import com.almostreliable.lootjs.loot.extension.LootParamsExtension; 5 | import net.minecraft.world.level.storage.loot.LootParams; 6 | import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet; 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 | @Mixin(LootParams.Builder.class) 13 | public abstract class LootParamsBuilderMixin { 14 | 15 | @Inject(method = "create", at = @At("RETURN")) 16 | public void lootjs$setType(LootContextParamSet params, CallbackInfoReturnable cir) { 17 | LootType type = LootType.getLootType(params); 18 | ((LootParamsExtension) cir.getReturnValue()).lootjs$setType(type); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/LootParamsMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.core.LootType; 4 | import com.almostreliable.lootjs.loot.extension.LootParamsExtension; 5 | import net.minecraft.world.level.storage.loot.LootParams; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Unique; 8 | 9 | @Mixin(LootParams.class) 10 | public class LootParamsMixin implements LootParamsExtension { 11 | 12 | @Unique 13 | private LootType lootjs$type = LootType.UNKNOWN; 14 | 15 | @Override 16 | public LootType lootjs$getType() { 17 | return lootjs$type; 18 | } 19 | 20 | @Override 21 | public void lootjs$setType(LootType type) { 22 | lootjs$type = type; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/LootPoolMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.loot.LootConditionList; 4 | import com.almostreliable.lootjs.loot.LootEntryList; 5 | import com.almostreliable.lootjs.loot.LootFunctionList; 6 | import com.almostreliable.lootjs.loot.extension.CompositeEntryBaseExtension; 7 | import com.almostreliable.lootjs.loot.extension.LootPoolExtension; 8 | import net.minecraft.world.item.ItemStack; 9 | import net.minecraft.world.level.storage.loot.LootContext; 10 | import net.minecraft.world.level.storage.loot.LootPool; 11 | import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; 12 | import net.minecraft.world.level.storage.loot.functions.LootItemFunction; 13 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 14 | import org.jetbrains.annotations.Nullable; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Mutable; 18 | import org.spongepowered.asm.mixin.Shadow; 19 | 20 | import java.util.List; 21 | import java.util.function.BiFunction; 22 | import java.util.function.Predicate; 23 | 24 | @Mixin(LootPool.class) 25 | public class LootPoolMixin implements LootPoolExtension { 26 | @Mutable @Shadow @Final private List entries; 27 | @Mutable @Shadow @Final private List conditions; 28 | @Mutable @Shadow @Final private Predicate compositeCondition; 29 | @Mutable @Shadow @Final private List functions; 30 | @Mutable @Shadow @Final private BiFunction compositeFunction; 31 | @Shadow @Nullable private String name; 32 | 33 | @Override 34 | public LootPool lootjs$asVanillaPool() { 35 | return (LootPool) (Object) this; 36 | } 37 | 38 | @Override 39 | public LootEntryList lootjs$getEntries() { 40 | var list = new LootEntryList(this.entries); 41 | this.entries = list.getElements(); 42 | return list; 43 | } 44 | 45 | @Override 46 | public LootConditionList lootjs$getConditions() { 47 | LootConditionList cl = new LootConditionList(this.conditions); 48 | this.conditions = cl.getElements(); 49 | this.compositeCondition = cl; 50 | return cl; 51 | } 52 | 53 | @Override 54 | public LootFunctionList lootjs$getFunctions() { 55 | LootFunctionList fl = new LootFunctionList(this.functions); 56 | this.functions = fl.getElements(); 57 | this.compositeFunction = fl; 58 | return fl; 59 | } 60 | 61 | @Override 62 | public void lootjs$setName(String name) { 63 | this.name = name; 64 | } 65 | 66 | @Override 67 | public void lootjs$recompose() { 68 | for (var entry : entries) { 69 | if (entry instanceof CompositeEntryBaseExtension ext) { 70 | ext.lootjs$recompose(); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/ReloadableServerRegistriesMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.LootEvents; 4 | import com.almostreliable.lootjs.LootJS; 5 | import net.minecraft.core.LayeredRegistryAccess; 6 | import net.minecraft.core.WritableRegistry; 7 | import net.minecraft.core.registries.Registries; 8 | import net.minecraft.server.RegistryLayer; 9 | import net.minecraft.server.ReloadableServerRegistries; 10 | import net.minecraft.world.level.storage.loot.LootTable; 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.callback.CallbackInfoReturnable; 15 | 16 | import java.util.List; 17 | 18 | @Mixin(value = ReloadableServerRegistries.class, priority = 1337) 19 | public class ReloadableServerRegistriesMixin { 20 | 21 | @Inject(method = "apply", at = @At("HEAD")) 22 | private static void lootjs$runLootTableEventJS(LayeredRegistryAccess arg, List> list, CallbackInfoReturnable> cir) { 23 | WritableRegistry registry = null; 24 | try { 25 | //noinspection unchecked 26 | registry = (WritableRegistry) list 27 | .stream() 28 | .filter(r -> r.key().equals(Registries.LOOT_TABLE)) 29 | .findFirst() 30 | .orElseThrow(() -> new IllegalStateException("Loot table registry not found")); 31 | } catch (Exception e) { 32 | LootJS.LOG.error("Failed to get loot table registry", e); 33 | } 34 | 35 | if (registry == null) { 36 | return; 37 | } 38 | 39 | LootJS.storeLookup(arg.compositeAccess()); 40 | LootEvents.invoke(registry); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/SetComponentsFunctionAccessor.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import net.minecraft.core.component.DataComponentPatch; 4 | import net.minecraft.world.level.storage.loot.functions.SetComponentsFunction; 5 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Invoker; 8 | import org.spongepowered.asm.mixin.throwables.MixinException; 9 | 10 | import java.util.List; 11 | 12 | @Mixin(SetComponentsFunction.class) 13 | public interface SetComponentsFunctionAccessor { 14 | 15 | @Invoker("") 16 | static SetComponentsFunction lootjs$create(List condition, DataComponentPatch components) { 17 | throw new MixinException("Invoker not found"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/SkeletonMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.LootModificationsAPI; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.world.damagesource.DamageSource; 6 | import net.minecraft.world.entity.monster.Skeleton; 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.CallbackInfo; 11 | 12 | @Mixin(Skeleton.class) 13 | public class SkeletonMixin { 14 | @Inject(method = "dropCustomDeathLoot", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/monster/Creeper;increaseDroppedSkulls()V"), cancellable = true) 15 | private void modifyWitherBossStarDrop(ServerLevel arg, DamageSource arg2, boolean bl, CallbackInfo ci) { 16 | if (LootModificationsAPI.DISABLE_SKELETON_DROPPING_HEAD) { 17 | ci.cancel(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/WitherBossMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.LootModificationsAPI; 4 | import net.minecraft.world.entity.boss.wither.WitherBoss; 5 | import net.minecraft.world.item.Items; 6 | import net.minecraft.world.level.ItemLike; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.ModifyArg; 10 | 11 | @Mixin(WitherBoss.class) 12 | public class WitherBossMixin { 13 | 14 | @ModifyArg(method = "dropCustomDeathLoot", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/boss/wither/WitherBoss;spawnAtLocation(Lnet/minecraft/world/level/ItemLike;)Lnet/minecraft/world/entity/item/ItemEntity;")) 15 | private ItemLike modifyWitherBossStarDrop(ItemLike item) { 16 | if (Items.NETHER_STAR == item && LootModificationsAPI.DISABLE_WITHER_DROPPING_NETHER_STAR) { 17 | return Items.AIR; 18 | } 19 | 20 | return item; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/ZombieMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin; 2 | 3 | import com.almostreliable.lootjs.LootModificationsAPI; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.world.damagesource.DamageSource; 6 | import net.minecraft.world.entity.monster.Zombie; 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.CallbackInfo; 11 | 12 | @Mixin(Zombie.class) 13 | public class ZombieMixin { 14 | @Inject(method = "dropCustomDeathLoot", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/monster/Creeper;increaseDroppedSkulls()V"), cancellable = true) 15 | private void modifyWitherBossStarDrop(ServerLevel arg, DamageSource arg2, boolean bl, CallbackInfo ci) { 16 | if (LootModificationsAPI.DISABLE_ZOMBIE_DROPPING_HEAD) { 17 | ci.cancel(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/forge/CommonHooksMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin.forge; 2 | 3 | import com.almostreliable.lootjs.LootModificationsAPI; 4 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.level.storage.loot.LootContext; 8 | import net.neoforged.neoforge.common.CommonHooks; 9 | import org.spongepowered.asm.mixin.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.CallbackInfoReturnable; 13 | 14 | @Mixin(CommonHooks.class) 15 | public class CommonHooksMixin { 16 | 17 | @Inject(method = "modifyLoot(Lnet/minecraft/resources/ResourceLocation;Lit/unimi/dsi/fastutil/objects/ObjectArrayList;Lnet/minecraft/world/level/storage/loot/LootContext;)Lit/unimi/dsi/fastutil/objects/ObjectArrayList;", at = @At("RETURN"), remap = false) 18 | private static void invokeActions(ResourceLocation lootTableID, ObjectArrayList loot, LootContext context, CallbackInfoReturnable> cir) { 19 | LootModificationsAPI.invokeActions(loot, context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/forge/LootModifierManagerMixin.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.mixin.forge; 2 | 3 | import com.almostreliable.lootjs.LootEvents; 4 | import com.almostreliable.lootjs.LootModificationsAPI; 5 | import com.google.common.collect.ImmutableMap; 6 | import com.google.gson.JsonElement; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.server.packs.resources.ResourceManager; 9 | import net.minecraft.util.profiling.ProfilerFiller; 10 | import net.neoforged.neoforge.common.loot.IGlobalLootModifier; 11 | import net.neoforged.neoforge.common.loot.LootModifierManager; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 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 | import java.util.HashMap; 19 | import java.util.Map; 20 | import java.util.Set; 21 | 22 | @Mixin(LootModifierManager.class) 23 | public class LootModifierManagerMixin { 24 | 25 | @Shadow(remap = false) private Map registeredLootModifiers; 26 | 27 | @Inject(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At("RETURN"), remap = false) 28 | private void lootjs$lootModifierReload(Map resourceList, ResourceManager resourceManagerIn, ProfilerFiller profilerIn, CallbackInfo ci) { 29 | Set locations = this.registeredLootModifiers.keySet(); 30 | LootModificationsAPI.reload(); 31 | 32 | Map modifiers = new HashMap<>(registeredLootModifiers); 33 | LootEvents.invokeModifiers(modifiers); 34 | registeredLootModifiers = ImmutableMap.copyOf(modifiers); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/forge/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.mixin.forge; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/mixin/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.mixin; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/BlockFilter.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.util; 2 | 3 | import net.minecraft.world.level.block.Block; 4 | import net.minecraft.world.level.block.state.BlockState; 5 | 6 | import java.util.function.Predicate; 7 | import java.util.stream.Stream; 8 | import java.util.stream.StreamSupport; 9 | 10 | public interface BlockFilter extends Iterable, Predicate { 11 | 12 | default Stream stream() { 13 | return StreamSupport.stream(spliterator(), false); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/DebugInfo.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.util; 2 | 3 | import com.almostreliable.lootjs.LootJS; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class DebugInfo { 9 | 10 | private final List lines = new ArrayList<>(); 11 | private int indent = 0; 12 | 13 | public void push() { 14 | this.indent++; 15 | } 16 | 17 | public void pop() { 18 | this.indent--; 19 | if (this.indent < 0) { 20 | throw new IllegalStateException("Indentation level is negative"); 21 | } 22 | } 23 | 24 | public void add(String s) { 25 | this.lines.add(new Line(this.indent, s)); 26 | } 27 | 28 | public void release() { 29 | StringBuilder sb = new StringBuilder(); 30 | sb.append("\n"); 31 | for (Line line : lines) { 32 | sb.append(" ".repeat(Math.max(0, line.indent))); 33 | sb.append(line.text); 34 | sb.append("\n"); 35 | } 36 | 37 | LootJS.DEBUG_ACTION.accept(sb.toString()); 38 | } 39 | 40 | private record Line(int indent, String text) { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/ListHolder.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.util; 2 | 3 | import dev.latvian.mods.rhino.util.HideFromJS; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | public abstract class ListHolder implements Iterable { 10 | 11 | protected final ArrayList elements; 12 | 13 | public static ArrayList asArrayList(List list) { 14 | if (list instanceof ArrayList) { 15 | return (ArrayList) list; 16 | } else { 17 | return new ArrayList<>(list); 18 | } 19 | } 20 | 21 | public ListHolder() { 22 | elements = new ArrayList<>(); 23 | } 24 | 25 | public ListHolder(List elements) { 26 | this.elements = asArrayList(elements); 27 | } 28 | 29 | @Override 30 | public abstract Iterator iterator(); 31 | 32 | protected abstract W wrap(T entry); 33 | 34 | protected abstract T unwrap(W entry); 35 | 36 | @HideFromJS 37 | public List getElements() { 38 | return elements; 39 | } 40 | 41 | public int size() { 42 | return elements.size(); 43 | } 44 | 45 | public boolean isEmpty() { 46 | return elements.isEmpty(); 47 | } 48 | 49 | public boolean add(W entry) { 50 | T wrappedElement = unwrap(entry); 51 | return elements.add(wrappedElement); 52 | } 53 | 54 | public void clear() { 55 | elements.clear(); 56 | } 57 | 58 | public W get(int index) { 59 | return wrap(elements.get(index)); 60 | } 61 | 62 | public W set(int index, W element) { 63 | var replaced = elements.set(index, unwrap(element)); 64 | return wrap(replaced); 65 | } 66 | 67 | public void add(int index, W element) { 68 | elements.add(index, unwrap(element)); 69 | } 70 | 71 | public W remove(int index) { 72 | T removedElement = elements.remove(index); 73 | return wrap(removedElement); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/LootContextUtils.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.util; 2 | 3 | import com.almostreliable.lootjs.loot.extension.LootContextExtension; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.level.storage.loot.LootContext; 7 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 8 | 9 | import javax.annotation.Nullable; 10 | 11 | public class LootContextUtils { 12 | @Nullable 13 | public static ServerPlayer getPlayerOrNull(LootContext context) { 14 | switch (LootContextExtension.cast(context).lootjs$getType()) { 15 | case BLOCK: 16 | case CHEST: 17 | return tryGetPlayer(context.getParamOrNull(LootContextParams.THIS_ENTITY)); 18 | case ENTITY: 19 | ServerPlayer player = tryGetPlayer(context.getParamOrNull(LootContextParams.ATTACKING_ENTITY)); 20 | if (player != null) { 21 | return player; 22 | } 23 | 24 | return tryGetPlayer(context.getParamOrNull(LootContextParams.LAST_DAMAGE_PLAYER)); 25 | case FISHING: 26 | return tryGetPlayer(context.getParamOrNull(LootContextParams.ATTACKING_ENTITY)); 27 | } 28 | 29 | return null; 30 | } 31 | 32 | @Nullable 33 | private static ServerPlayer tryGetPlayer(@Nullable Entity entity) { 34 | if (entity instanceof ServerPlayer) { 35 | return (ServerPlayer) entity; 36 | } 37 | 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/LootObjectList.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.util; 2 | 3 | 4 | import com.almostreliable.lootjs.core.filters.IdFilter; 5 | 6 | import javax.annotation.Nullable; 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.ListIterator; 10 | 11 | public abstract class LootObjectList extends ArrayList { 12 | 13 | public LootObjectList() { 14 | super(); 15 | } 16 | 17 | public LootObjectList(int initialCapacity) { 18 | super(initialCapacity); 19 | } 20 | 21 | public LootObjectList(Collection entries) { 22 | super(entries); 23 | } 24 | 25 | public void removeById(IdFilter filter) { 26 | removeIf(entry -> entryMatches(entry, filter)); 27 | } 28 | 29 | public void transform(NullableFunction onTransform) { 30 | ListIterator it = listIterator(); 31 | while (it.hasNext()) { 32 | T t = it.next(); 33 | Object o = onTransform.apply(t); 34 | T transformed = wrapTransformed(o); 35 | if (transformed == null) { 36 | it.remove(); 37 | continue; 38 | } 39 | 40 | if (transformed != t) { 41 | it.set(transformed); 42 | } 43 | } 44 | } 45 | 46 | @Nullable 47 | protected abstract T wrapTransformed(@Nullable Object o); 48 | 49 | protected abstract boolean entryMatches(T entry, IdFilter filter); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/NullableFunction.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.util; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | @FunctionalInterface 6 | public interface NullableFunction { 7 | 8 | @Nullable 9 | R apply(T t); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.almostreliable.lootjs.util; 2 | 3 | import net.minecraft.core.registries.BuiltInRegistries; 4 | import net.minecraft.resources.ResourceLocation; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.phys.Vec3; 8 | 9 | import javax.annotation.Nullable; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.stream.Collectors; 14 | 15 | public class Utils { 16 | 17 | @SuppressWarnings("unchecked") 18 | public static T cast(Object o) { 19 | return (T) o; 20 | } 21 | 22 | public static String getClassNameEnding(T t) { 23 | String tName = t.getClass().getName(); 24 | return tName.substring(tName.lastIndexOf('.') + 1); 25 | } 26 | 27 | @SuppressWarnings("unchecked") 28 | public static Map mapOrThrow(Object o) { 29 | if (o instanceof Map m) { 30 | return (Map) m; 31 | } 32 | 33 | throw new RuntimeException("Expected map, got " + o.getClass().getName()); 34 | } 35 | 36 | @SuppressWarnings("unchecked") 37 | public static List listOrThrow(Object o) { 38 | if (o instanceof List l) { 39 | return (List) l; 40 | } 41 | 42 | throw new RuntimeException("Expected list, got " + o.getClass().getName()); 43 | } 44 | 45 | public static String formatEntity(Entity entity) { 46 | return String.format("Type=%s, Id=%s, Dim=%s, x=%.2f, y=%.2f, z=%.2f", 47 | quote(BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType())), 48 | entity.getId(), 49 | quote(entity.level().dimension().location()), 50 | entity.getX(), 51 | entity.getY(), 52 | entity.getZ()); 53 | } 54 | 55 | public static String formatPosition(Vec3 position) { 56 | return String.format("(%.2f, %.2f, %.2f)", position.x, position.y, position.z); 57 | } 58 | 59 | public static String formatItemStack(ItemStack itemStack) { 60 | return itemStack.toString(); 61 | } 62 | 63 | public static String quote(String s) { 64 | return "\"" + s + "\""; 65 | } 66 | 67 | public static String quote(@Nullable ResourceLocation rl) { 68 | return quote(rl == null ? "NO_LOCATION" : rl.toString()); 69 | } 70 | 71 | public static String quote(String prefix, Collection objects) { 72 | return prefix + "[" + 73 | objects.stream().map(Object::toString).map(Utils::quote).collect(Collectors.joining(",")) + "]"; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/almostreliable/lootjs/util/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package com.almostreliable.lootjs.util; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /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 = "lootjs.mixins.json" 36 | -------------------------------------------------------------------------------- /src/main/resources/data/lootjs/structure/empty_test_structure.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlmostReliable/lootjs/21bc16fd6f5e2a75d86f2378f36354d2ad82db41/src/main/resources/data/lootjs/structure/empty_test_structure.nbt -------------------------------------------------------------------------------- /src/main/resources/kubejs.plugins.txt: -------------------------------------------------------------------------------- 1 | com.almostreliable.lootjs.kube.LootJSPlugin 2 | -------------------------------------------------------------------------------- /src/main/resources/lootjs.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required" : true, 3 | "package" : "com.almostreliable.lootjs.mixin", 4 | "compatibilityLevel" : "JAVA_21", 5 | "mixins" : [ 6 | "CompositeEntryBaseMixin", 7 | "CreeperMixin", 8 | "EntityPredicateMixin", 9 | "HolderSetCodecMixin", 10 | "ItemPredicateMixin", 11 | "LootContextMixin", 12 | "LootItemConditionalFunctionMixin", 13 | "LootItemConditionMixin", 14 | "LootItemFunctionMixin", 15 | "LootParamsBuilderMixin", 16 | "LootParamsMixin", 17 | "LootPoolMixin", 18 | "LootTableMixin", 19 | "ReloadableServerRegistriesMixin", 20 | "SetComponentsFunctionAccessor", 21 | "SkeletonMixin", 22 | "WitherBossMixin", 23 | "ZombieMixin", 24 | "forge.CommonHooksMixin", 25 | "forge.LootModifierManagerMixin" 26 | ], 27 | "injectors" : { 28 | "defaultRequire" : 1 29 | }, 30 | "minVersion" : "0.8" 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/testmod/TestMod.java: -------------------------------------------------------------------------------- 1 | package testmod; 2 | 3 | import com.almostreliable.lootjs.LootEvents; 4 | import com.almostreliable.lootjs.loot.LootTableEvent; 5 | import net.neoforged.fml.common.Mod; 6 | import net.neoforged.neoforge.gametest.GameTestHooks; 7 | import testmod.event.Entry; 8 | 9 | @Mod("testmod") 10 | public class TestMod { 11 | 12 | public TestMod() { 13 | if (GameTestHooks.isGametestEnabled()) { 14 | LootEvents.listen(registry -> { 15 | var event = new LootTableEvent(registry); 16 | Entry.init(event); 17 | }); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/testmod/event/Entry.java: -------------------------------------------------------------------------------- 1 | package testmod.event; 2 | 3 | import com.almostreliable.lootjs.core.entry.LootEntry; 4 | import com.almostreliable.lootjs.loot.LootTableEvent; 5 | import net.minecraft.world.entity.EntityType; 6 | import net.minecraft.world.item.Items; 7 | 8 | public class Entry { 9 | 10 | public static final String CREEPER_TEST_POOL = "creeper_test_pool"; 11 | 12 | public static void init(LootTableEvent event) { 13 | event.getEntityTable(EntityType.COW).clear().createPool().addEntry(LootEntry.ofItem(Items.DIAMOND)); 14 | 15 | event.getEntityTable(EntityType.CREEPER).createPool(pool -> { 16 | //pool.addEntry(LootEntry.ofItem(Items.DIAMOND_SWORD).enchant(builder -> { 17 | // builder. 18 | //})) 19 | }); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/testmod/event/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package testmod.event; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/ConditionsContainer.java: -------------------------------------------------------------------------------- 1 | package testmod.gametest; 2 | 3 | import com.almostreliable.lootjs.BuildConfig; 4 | import com.almostreliable.lootjs.loot.LootCondition; 5 | import net.minecraft.advancements.critereon.EntityPredicate; 6 | import net.minecraft.gametest.framework.GameTest; 7 | import net.minecraft.gametest.framework.GameTestHelper; 8 | import net.minecraft.world.level.storage.loot.LootContext; 9 | import net.minecraft.world.level.storage.loot.predicates.LootItemEntityPropertyCondition; 10 | import net.neoforged.neoforge.gametest.GameTestHolder; 11 | import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; 12 | 13 | @GameTestHolder(value = BuildConfig.MOD_ID) 14 | @PrefixGameTestTemplate(false) 15 | public class ConditionsContainer { 16 | 17 | @GameTest(template = GameTestTemplates.EMPTY) 18 | public void entityTarget_Entity(GameTestHelper helper) { 19 | helper.succeedIf(() -> { 20 | var condition = (LootItemEntityPropertyCondition) new LootCondition().matchEntity(EntityPredicate.Builder 21 | .entity() 22 | .build()); 23 | GameTestUtils.assertEquals(helper, 24 | condition.entityTarget(), 25 | LootContext.EntityTarget.THIS); 26 | }); 27 | } 28 | 29 | @GameTest(template = GameTestTemplates.EMPTY) 30 | public void entityTarget_Killer(GameTestHelper helper) { 31 | helper.succeedIf(() -> { 32 | var condition = (LootItemEntityPropertyCondition) new LootCondition().matchAttacker(EntityPredicate.Builder 33 | .entity() 34 | .build()); 35 | GameTestUtils.assertEquals(helper, 36 | condition.entityTarget(), 37 | LootContext.EntityTarget.ATTACKER); 38 | }); 39 | } 40 | 41 | @GameTest(template = GameTestTemplates.EMPTY) 42 | public void entityTarget_DirectKiller(GameTestHelper helper) { 43 | helper.succeedIf(() -> { 44 | var condition = (LootItemEntityPropertyCondition) new LootCondition().matchDirectAttacker(EntityPredicate.Builder 45 | .entity() 46 | .build()); 47 | GameTestUtils.assertEquals(helper, 48 | condition.entityTarget(), 49 | LootContext.EntityTarget.DIRECT_ATTACKER); 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/GameTestTemplates.java: -------------------------------------------------------------------------------- 1 | package testmod.gametest; 2 | 3 | public class GameTestTemplates { 4 | public static final String EMPTY = "empty_test_structure"; 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/GameTestUtilsTests.java: -------------------------------------------------------------------------------- 1 | package testmod.gametest; 2 | 3 | import com.almostreliable.lootjs.BuildConfig; 4 | import net.minecraft.gametest.framework.GameTest; 5 | import net.minecraft.gametest.framework.GameTestHelper; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.item.Items; 9 | import net.minecraft.world.level.GameType; 10 | import net.minecraft.world.level.storage.loot.LootContext; 11 | import net.neoforged.neoforge.gametest.GameTestHolder; 12 | import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; 13 | 14 | @GameTestHolder(value = BuildConfig.MOD_ID) 15 | @PrefixGameTestTemplate(false) 16 | public class GameTestUtilsTests { 17 | 18 | @GameTest(template = GameTestTemplates.EMPTY) 19 | public void fillExampleLoot(GameTestHelper helper) { 20 | helper.succeedIf(() -> { 21 | Player p = helper.makeMockPlayer(GameType.SURVIVAL); 22 | LootContext ctx = GameTestUtils.chestContext(helper.getLevel(), p.position(), p); 23 | var loot = GameTestUtils.fillExampleLoot(ctx); 24 | 25 | ItemStack diamond = loot.get(0); 26 | ItemStack netherBrick = loot.get(1); 27 | ItemStack goldenChestPlate = loot.get(2); 28 | 29 | GameTestUtils.assertEquals(helper, diamond.getItem(), Items.DIAMOND); 30 | GameTestUtils.assertEquals(helper, netherBrick.getItem(), Items.NETHER_BRICK); 31 | GameTestUtils.assertEquals(helper, netherBrick.getCount(), 10); 32 | GameTestUtils.assertEquals(helper, goldenChestPlate.getItem(), Items.GOLDEN_CHESTPLATE); 33 | GameTestUtils.assertTrue(helper, loot.size() == 3, "Should contain 3 items"); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/conditions/BiomeCheckTest.java: -------------------------------------------------------------------------------- 1 | package testmod.gametest.conditions; 2 | 3 | import com.almostreliable.lootjs.BuildConfig; 4 | import com.almostreliable.lootjs.loot.condition.MatchBiome; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.core.Holder; 7 | import net.minecraft.core.HolderSet; 8 | import net.minecraft.core.Registry; 9 | import net.minecraft.core.registries.Registries; 10 | import net.minecraft.gametest.framework.GameTest; 11 | import net.minecraft.gametest.framework.GameTestHelper; 12 | import net.minecraft.resources.ResourceLocation; 13 | import net.minecraft.tags.BiomeTags; 14 | import net.minecraft.world.entity.player.Player; 15 | import net.minecraft.world.level.GameType; 16 | import net.minecraft.world.level.biome.Biome; 17 | import net.minecraft.world.level.storage.loot.LootContext; 18 | import net.minecraft.world.phys.Vec3; 19 | import net.neoforged.neoforge.gametest.GameTestHolder; 20 | import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; 21 | import testmod.gametest.GameTestTemplates; 22 | import testmod.gametest.GameTestUtils; 23 | 24 | @GameTestHolder(value = BuildConfig.MOD_ID) 25 | @PrefixGameTestTemplate(false) 26 | public class BiomeCheckTest { 27 | 28 | private static final BlockPos TEST_POS = new BlockPos(1, 0, 1); 29 | 30 | @GameTest(template = GameTestTemplates.EMPTY) 31 | public void BiomeCheck_match(GameTestHelper helper) { 32 | Holder biomeHolder = helper.getLevel().getBiome(TEST_POS); 33 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), Vec3.atLowerCornerOf(TEST_POS)); 34 | MatchBiome check = new MatchBiome(HolderSet.direct(biomeHolder)); 35 | helper.succeedIf(() -> GameTestUtils.assertTrue(helper, 36 | check.test(ctx), 37 | "Biome " + biomeHolder + " check should pass")); 38 | } 39 | 40 | @GameTest(template = GameTestTemplates.EMPTY) 41 | public void BiomeCheck_fail(GameTestHelper helper) { 42 | Player player = helper.makeMockPlayer(GameType.SURVIVAL); 43 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), player.position()); 44 | 45 | var biomeReference = helper 46 | .getLevel() 47 | .registryAccess() 48 | .registryOrThrow(Registries.BIOME) 49 | .getHolder(ResourceLocation.parse("minecraft:deep_ocean")) 50 | .orElseThrow(); 51 | MatchBiome check = new MatchBiome(HolderSet.direct(biomeReference)); 52 | helper.succeedIf(() -> GameTestUtils.assertFalse(helper, 53 | check.test(ctx), 54 | "Biome " + biomeReference.key() + " check should not pass")); 55 | } 56 | 57 | @GameTest(template = GameTestTemplates.EMPTY) 58 | public void BiomeCheck_matchTags(GameTestHelper helper) { 59 | Player player = helper.makeMockPlayer(GameType.SURVIVAL); 60 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), player.position()); 61 | Registry biomes = helper.getLevel().registryAccess().registryOrThrow(Registries.BIOME); 62 | MatchBiome check = new MatchBiome(biomes.getOrCreateTag(BiomeTags.IS_OVERWORLD)); 63 | helper.succeedIf(() -> GameTestUtils.assertTrue(helper, 64 | check.test(ctx), 65 | "Biome " + BiomeTags.IS_OVERWORLD + " tag check should pass")); 66 | } 67 | 68 | @GameTest(template = GameTestTemplates.EMPTY) 69 | public void BiomeCheck_failAllTags(GameTestHelper helper) { 70 | Player player = helper.makeMockPlayer(GameType.SURVIVAL); 71 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), player.position()); 72 | Registry biomes = helper.getLevel().registryAccess().registryOrThrow(Registries.BIOME); 73 | MatchBiome check = new MatchBiome(biomes.getOrCreateTag(BiomeTags.IS_NETHER)); 74 | helper.succeedIf(() -> GameTestUtils.assertFalse(helper, 75 | check.test(ctx), 76 | "Biome " + BiomeTags.IS_NETHER + " tag check should not pass")); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/conditions/DimensionCheckTest.java: -------------------------------------------------------------------------------- 1 | package testmod.gametest.conditions; 2 | 3 | import com.almostreliable.lootjs.BuildConfig; 4 | import com.almostreliable.lootjs.loot.condition.MatchDimension; 5 | import net.minecraft.gametest.framework.GameTest; 6 | import net.minecraft.gametest.framework.GameTestHelper; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.level.storage.loot.LootContext; 9 | import net.minecraft.world.phys.Vec3; 10 | import net.neoforged.neoforge.gametest.GameTestHolder; 11 | import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; 12 | import testmod.gametest.GameTestTemplates; 13 | import testmod.gametest.GameTestUtils; 14 | 15 | @GameTestHolder(value = BuildConfig.MOD_ID) 16 | @PrefixGameTestTemplate(false) 17 | public class DimensionCheckTest { 18 | private static final Vec3 TEST_POS = new Vec3(1, 0, 1); 19 | 20 | @GameTest(template = GameTestTemplates.EMPTY) 21 | public void AnyDimension_match(GameTestHelper helper) { 22 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), TEST_POS); 23 | 24 | MatchDimension owDim = new MatchDimension(new ResourceLocation[]{ 25 | ResourceLocation.parse("overworld") 26 | }); 27 | helper.succeedIf(() -> GameTestUtils.assertTrue(helper, 28 | owDim.test(ctx), 29 | "Is in overworld check should pass")); 30 | } 31 | 32 | @GameTest(template = GameTestTemplates.EMPTY) 33 | public void AnyDimension_fail(GameTestHelper helper) { 34 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), TEST_POS); 35 | 36 | MatchDimension owDim = new MatchDimension(new ResourceLocation[]{ 37 | ResourceLocation.parse("nether") 38 | }); 39 | helper.succeedIf(() -> GameTestUtils.assertFalse(helper, 40 | owDim.test(ctx), 41 | "Is in overworld check should fail")); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/conditions/IsLightLevelTest.java: -------------------------------------------------------------------------------- 1 | package testmod.gametest.conditions; 2 | 3 | import com.almostreliable.lootjs.BuildConfig; 4 | import testmod.gametest.GameTestTemplates; 5 | import testmod.gametest.GameTestUtils; 6 | import com.almostreliable.lootjs.loot.condition.IsLightLevel; 7 | import net.minecraft.gametest.framework.GameTest; 8 | import net.minecraft.gametest.framework.GameTestHelper; 9 | import net.minecraft.world.level.storage.loot.LootContext; 10 | import net.minecraft.world.phys.Vec3; 11 | import net.neoforged.neoforge.gametest.GameTestHolder; 12 | import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; 13 | 14 | @GameTestHolder(value = BuildConfig.MOD_ID) 15 | @PrefixGameTestTemplate(false) 16 | public class IsLightLevelTest { 17 | private static final Vec3 TEST_POS = new Vec3(0, 1, 0); 18 | 19 | @GameTest(template = GameTestTemplates.EMPTY) 20 | public void matchLight(GameTestHelper helper) { 21 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), TEST_POS); 22 | 23 | IsLightLevel isLightLevel = new IsLightLevel(0, 15); 24 | helper.succeedIf(() -> GameTestUtils.assertTrue(helper, 25 | isLightLevel.test(ctx), 26 | "IsLightLevel check should pass")); 27 | } 28 | 29 | @GameTest(template = GameTestTemplates.EMPTY) 30 | public void failMatchLight(GameTestHelper helper) { 31 | LootContext ctx = GameTestUtils.unknownContext(helper.getLevel(), TEST_POS); 32 | 33 | IsLightLevel isLightLevel = new IsLightLevel(15, 15); 34 | helper.succeedIf(() -> GameTestUtils.assertFalse(helper, 35 | isLightLevel.test(ctx), 36 | "IsLightLevel check should fail")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/conditions/MatchKillerDistanceTest.java: -------------------------------------------------------------------------------- 1 | package testmod.gametest.conditions; 2 | 3 | import com.almostreliable.lootjs.BuildConfig; 4 | import com.almostreliable.lootjs.loot.condition.MatchKillerDistance; 5 | import com.almostreliable.lootjs.loot.condition.builder.DistancePredicateBuilder; 6 | import net.minecraft.advancements.critereon.MinMaxBounds; 7 | import net.minecraft.core.BlockPos; 8 | import net.minecraft.gametest.framework.GameTest; 9 | import net.minecraft.gametest.framework.GameTestHelper; 10 | import net.minecraft.world.damagesource.DamageSource; 11 | import net.minecraft.world.entity.EntityType; 12 | import net.minecraft.world.entity.animal.Cow; 13 | import net.minecraft.world.entity.player.Player; 14 | import net.minecraft.world.level.GameType; 15 | import net.minecraft.world.level.storage.loot.LootContext; 16 | import net.minecraft.world.level.storage.loot.LootParams; 17 | import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; 18 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 19 | import net.neoforged.neoforge.gametest.GameTestHolder; 20 | import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; 21 | import testmod.gametest.GameTestTemplates; 22 | import testmod.gametest.GameTestUtils; 23 | 24 | import java.util.Optional; 25 | 26 | @GameTestHolder(value = BuildConfig.MOD_ID) 27 | @PrefixGameTestTemplate(false) 28 | public class MatchKillerDistanceTest { 29 | private static final BlockPos TEST_POS = new BlockPos(0, 0, 0); 30 | 31 | @GameTest(template = GameTestTemplates.EMPTY) 32 | public void matchDistance(GameTestHelper helper) { 33 | Player player = helper.makeMockPlayer(GameType.SURVIVAL); 34 | Cow cow = GameTestUtils.simpleEntity(EntityType.COW, helper.getLevel(), TEST_POS); 35 | 36 | DamageSource ds = cow.damageSources().playerAttack(player); 37 | var params = new LootParams.Builder(helper.getLevel()) 38 | .withParameter(LootContextParams.THIS_ENTITY, cow) 39 | .withParameter(LootContextParams.ORIGIN, cow.position()) 40 | .withParameter(LootContextParams.DAMAGE_SOURCE, ds) 41 | .withOptionalParameter(LootContextParams.ATTACKING_ENTITY, ds.getEntity()) 42 | .withOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY, ds.getDirectEntity()) 43 | .withParameter(LootContextParams.LAST_DAMAGE_PLAYER, player) 44 | .withLuck(player.getLuck()) 45 | .create(LootContextParamSets.ENTITY); 46 | 47 | LootContext ctx = new LootContext.Builder(params).create(Optional.empty()); 48 | 49 | double dist = player.distanceTo(cow); 50 | MatchKillerDistance mkd = new MatchKillerDistance(new DistancePredicateBuilder() 51 | .absolute(MinMaxBounds.Doubles.between(dist - 1f, dist + 1f)) 52 | .build()); 53 | helper.succeedIf(() -> GameTestUtils.assertTrue(helper, 54 | mkd.test(ctx), 55 | "MatchKillerDistance check should pass")); 56 | } 57 | 58 | @GameTest(template = GameTestTemplates.EMPTY) 59 | public void failDistance(GameTestHelper helper) { 60 | Player player = helper.makeMockPlayer(GameType.SURVIVAL); 61 | Cow cow = GameTestUtils.simpleEntity(EntityType.COW, helper.getLevel(), TEST_POS); 62 | 63 | DamageSource ds = cow.damageSources().playerAttack(player); 64 | var params = new LootParams.Builder(helper.getLevel()) 65 | .withParameter(LootContextParams.THIS_ENTITY, cow) 66 | .withParameter(LootContextParams.ORIGIN, cow.position()) 67 | .withParameter(LootContextParams.DAMAGE_SOURCE, ds) 68 | .withOptionalParameter(LootContextParams.ATTACKING_ENTITY, ds.getEntity()) 69 | .withOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY, ds.getDirectEntity()) 70 | .withParameter(LootContextParams.LAST_DAMAGE_PLAYER, player) 71 | .withLuck(player.getLuck()) 72 | .create(LootContextParamSets.ENTITY); 73 | 74 | LootContext ctx = new LootContext.Builder(params).create(Optional.empty()); 75 | 76 | MatchKillerDistance mkd = new MatchKillerDistance(new DistancePredicateBuilder() 77 | .absolute(MinMaxBounds.Doubles.between(1000d, 1000d)) 78 | .build()); 79 | helper.succeedIf(() -> GameTestUtils.assertFalse(helper, 80 | mkd.test(ctx), 81 | "MatchKillerDistance check should fail")); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package testmod.gametest; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/test/java/testmod/gametest/tables/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault 2 | package testmod.gametest.tables; 3 | 4 | import net.minecraft.MethodsReturnNonnullByDefault; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | -------------------------------------------------------------------------------- /src/test/java/testmod/mixin/ScriptManagerMixin.java: -------------------------------------------------------------------------------- 1 | package testmod.mixin; 2 | 3 | 4 | import com.almostreliable.lootjs.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(J)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/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/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 | [[mixins]] 11 | config = "testmod.mixins.json" 12 | -------------------------------------------------------------------------------- /src/test/resources/testmod.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "testmod.mixin", 4 | "compatibilityLevel" : "JAVA_21", 5 | "mixins": [ 6 | "ScriptManagerMixin" 7 | ], 8 | "injectors": { 9 | "defaultRequire": 1 10 | }, 11 | "minVersion": "0.8" 12 | } 13 | --------------------------------------------------------------------------------