|
| 1 | +# Advancements |
| 2 | + |
| 3 | +Azimuth provides a Create-compatible advancement system that closely mirrors how Create handles its own advancements internally. The goal is to let addon authors define and award advancements without having to wire up all the boilerplate themselves and, importantly, without needing to know Create's internals. |
| 4 | + |
| 5 | +There are three main classes involved: |
| 6 | + |
| 7 | +| Class | Role | |
| 8 | +| --- | --- | |
| 9 | +| `AzimuthAdvancementProvider` | Central registry for a mod's advancements. Handles datagen and lang generation. | |
| 10 | +| `AzimuthAdvancement` | A single advancement definition icon, title, description, trigger, parent. | |
| 11 | +| `AzimuthAdvancementBehaviour` | A `BlockEntityBehaviour` for tracking and awarding advancements from block entities. | |
| 12 | + |
| 13 | + |
| 14 | +## Setting up `AzimuthAdvancementProvider` |
| 15 | + |
| 16 | +Create one static `AzimuthAdvancementProvider` for your mod and define all your advancements on it: |
| 17 | + |
| 18 | +```java |
| 19 | +public class MyAdvancements { |
| 20 | + |
| 21 | + public static final AzimuthAdvancementProvider PROVIDER = |
| 22 | + new AzimuthAdvancementProvider(MyMod.MOD_ID, "My Mod Advancements"); |
| 23 | + |
| 24 | + public static final AzimuthAdvancement ROOT = PROVIDER.create("root", b -> b |
| 25 | + .icon(MyItems.MY_ITEM) |
| 26 | + .title("My Mod") |
| 27 | + .description("Get started with My Mod.") |
| 28 | + .after(() -> AllAdvancements.ROOT) // display inside the Create tab omit to create a new advancement tab |
| 29 | + .awardedForFree() // immediately award when the player first joins |
| 30 | + .special(AzimuthAdvancement.TaskType.SILENT) |
| 31 | + ); |
| 32 | + |
| 33 | + public static final AzimuthAdvancement FIRST_CRAFT = PROVIDER.create("first_craft", b -> b |
| 34 | + .icon(MyItems.MY_ITEM) |
| 35 | + .title("First Steps") |
| 36 | + .description("Craft your first widget.") |
| 37 | + .after(ROOT) |
| 38 | + .whenItemCollected(MyItems.MY_ITEM) |
| 39 | + ); |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +Then wire it into datagen. Call `register()` during common mod init, then hook `provideLang` and `dataProvider` into your datagen event: |
| 44 | + |
| 45 | +```java |
| 46 | +// In your mod class or init method |
| 47 | +MyAdvancements.PROVIDER.register(); |
| 48 | + |
| 49 | +// In your GatherDataEvent handler |
| 50 | +public static void gatherData(final GatherDataEvent event) { |
| 51 | + final PackOutput output = event.getGenerator().getPackOutput(); |
| 52 | + final CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider(); |
| 53 | + |
| 54 | + YourMod.REGISTRATE.addDataGenerator(ProviderType.LANG, provider -> { |
| 55 | + final BiConsumer<String, String> langConsumer = provider::add; |
| 56 | + MyAdvancements.PROVIDER.provideLang(langConsumer); |
| 57 | + }); |
| 58 | + |
| 59 | + event.getGenerator().addProvider( |
| 60 | + event.includeServer(), |
| 61 | + MyAdvancements.PROVIDER.dataProvider(output, lookupProvider) |
| 62 | + ); |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | + |
| 67 | +## Advancement builder options |
| 68 | + |
| 69 | +The builder passed to `PROVIDER.create(...)` supports the following: |
| 70 | + |
| 71 | +### Icon |
| 72 | + |
| 73 | +```java |
| 74 | +.icon(MyItems.MY_ITEM) // ItemProviderEntry<?, ?> |
| 75 | +.icon(Items.IRON_INGOT) // ItemLike |
| 76 | +.icon(new ItemStack(Items.BOOK)) // ItemStack, for NBT-specific icons |
| 77 | +``` |
| 78 | + |
| 79 | +### Title and description |
| 80 | + |
| 81 | +```java |
| 82 | +.title("My Advancement Title") |
| 83 | +.description("A short description shown in the advancement screen.") |
| 84 | +``` |
| 85 | + |
| 86 | +Lang keys are automatically generated as `advancement.<modid>.<id>` and `advancement.<modid>.<id>.desc`. |
| 87 | + |
| 88 | +### Parent |
| 89 | + |
| 90 | +Chain advancements by passing the parent definition: |
| 91 | + |
| 92 | +```java |
| 93 | +.after(ROOT) // AzimuthAdvancement |
| 94 | +.after(AllAdvancements.ANDESITE_ALLOY) // CreateAdvancement |
| 95 | +.after(ResourceLocation.fromNamespaceAndPath(...)) // raw ResourceLocation |
| 96 | +``` |
| 97 | + |
| 98 | +Leave out `.after(...)` entirely for the root advancement of your tree (its background texture is expected at `textures/gui/advancements.png` in your mod namespace). |
| 99 | + |
| 100 | +### Task type |
| 101 | + |
| 102 | +```java |
| 103 | +.special(AzimuthAdvancement.TaskType.NORMAL) // default |
| 104 | +``` |
| 105 | + |
| 106 | +| Type | Toast | Announce | Hidden | |
| 107 | +| --- | --- | --- | --- | |
| 108 | +| `SILENT` | No | No | No | |
| 109 | +| `NORMAL` | Yes | No | No | |
| 110 | +| `NOISY` | Yes | Yes | No | |
| 111 | +| `EXPERT` | Yes | Yes | No | |
| 112 | +| `SECRET` | Yes | Yes | Yes | |
| 113 | + |
| 114 | +`SECRET` also appends a "Hidden Advancement" suffix to the description automatically. |
| 115 | + |
| 116 | +### Triggers |
| 117 | + |
| 118 | +If you want the advancement to be awarded by a game event (rather than manually), use one of the built-in trigger helpers: |
| 119 | + |
| 120 | +```java |
| 121 | +.whenBlockPlaced(MyBlocks.MY_BLOCK.get()) // placed a specific block |
| 122 | +.whenItemCollected(MyItems.MY_ITEM) // item entered inventory |
| 123 | +.whenItemCollected(MyTags.Items.MY_TAG) // tag variant |
| 124 | +.whenIconCollected() // shorthand for the icon item |
| 125 | +.awardedForFree() // always awarded (useful for roots) |
| 126 | +.externalTrigger(myCriterion) // raw Criterion<?> for anything else |
| 127 | +``` |
| 128 | + |
| 129 | +If none of these are called, a built-in `SimpleCreateTrigger` is created automatically. You can then award the advancement manually (see below). |
| 130 | + |
| 131 | + |
| 132 | +## Awarding advancements manually |
| 133 | + |
| 134 | +If the advancement uses the built-in trigger (no `.whenX` call), award it directly: |
| 135 | + |
| 136 | +```java |
| 137 | +MyAdvancements.FIRST_CRAFT.awardTo(player); |
| 138 | +``` |
| 139 | + |
| 140 | +You can also check whether a player already has it: |
| 141 | + |
| 142 | +```java |
| 143 | +if (!MyAdvancements.FIRST_CRAFT.isAlreadyAwardedTo(player)) { |
| 144 | + // do something |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +`awardTo` will throw `UnsupportedOperationException` if the advancement uses an external trigger. |
| 149 | + |
| 150 | + |
| 151 | +## `AzimuthAdvancementBehaviour` |
| 152 | + |
| 153 | +If you want a block entity to track a player and award advancements when they interact with it, add `AzimuthAdvancementBehaviour` to its behaviour list: |
| 154 | + |
| 155 | +```java |
| 156 | +@Override |
| 157 | +public void addBehaviours(List<BlockEntityBehaviour> behaviours) { |
| 158 | + super.addBehaviours(behaviours); |
| 159 | + AzimuthAdvancementBehaviour.create(behaviours, this, |
| 160 | + MyAdvancements.FIRST_CRAFT, |
| 161 | + MyAdvancements.ANOTHER_ONE |
| 162 | + ); |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +Using the static `create` method (rather than constructing directly) means that if multiple callers add the behaviour, they'll merge into a single instance rather than duplicate. |
| 167 | + |
| 168 | +### Tracking the player |
| 169 | + |
| 170 | +Call `setPlacedBy` from your block's `setPlacedBy` override to register the player who placed it: |
| 171 | + |
| 172 | +```java |
| 173 | +@Override |
| 174 | +public void setPlacedBy(Level level, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack) { |
| 175 | + super.setPlacedBy(level, pos, state, placer, stack); |
| 176 | + AzimuthAdvancementBehaviour.setPlacedBy(level, pos, placer); |
| 177 | +} |
| 178 | +``` |
| 179 | + |
| 180 | +Fake players are ignored automatically. |
| 181 | + |
| 182 | +### Awarding from the behaviour |
| 183 | + |
| 184 | +```java |
| 185 | +// Award if the player is within maxDistance blocks |
| 186 | +behaviour.awardPlayerIfNear(MyAdvancements.FIRST_CRAFT, 8); |
| 187 | + |
| 188 | +// Award unconditionally |
| 189 | +behaviour.awardPlayer(MyAdvancements.FIRST_CRAFT); |
| 190 | +``` |
| 191 | + |
| 192 | +Or use the static shorthand if you only have a position: |
| 193 | + |
| 194 | +```java |
| 195 | +AzimuthAdvancementBehaviour.tryAward(level, pos, MyAdvancements.FIRST_CRAFT); |
| 196 | +``` |
| 197 | + |
| 198 | +Already-awarded advancements are pruned from the behaviour's tracked set automatically. Once all tracked advancements for a player have been awarded, the player reference is cleared. |
0 commit comments