├── .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 extends Registry extends T>> 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