Создание достижений 26.1.2
Руководство по настройке генерации достижений через datagen.
ТРЕБОВАНИЯ
Сначала убедитесь, что вы установили datagen.
Установка
Для начала нам нужно создать провайдера. Создайте класс, который расширяет FabricAdvancementProvider и заполните базовые методы:
java
public class ExampleModAdvancementProvider extends FabricAdvancementProvider {
protected ExampleModAdvancementProvider(FabricPackOutput output, CompletableFuture<HolderLookup.Provider> registryLookup) {
super(output, registryLookup);
}
@Override
public void generateAdvancement(HolderLookup.Provider wrapperLookup, Consumer<AdvancementHolder> consumer) {
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Чтобы завершить настройку, добавьте этот провайдер к вашей DataGeneratorEntrypoint в методе onInitializeDataGenerator.
java
pack.addProvider(ExampleModAdvancementProvider::new);1
Структура достижений
Достижение состоит из нескольких различных компонентов. Помимо требований, называемых "критериями" (criterion), оно может содержать:
DisplayInfo— указывает игре, как именно отображать достижение игрокам.AdvancementRequirements— списки списков критериев; для выполнения достижения требуется завершить как минимум один критерий из каждого подсписка.AdvancementRewards— награды, которые игрок получает за выполнение достижения.Strategy— стратегия, которая определяет, как достижение обрабатывает несколько критериев.Parent— родительское достижение. Организует иерархию, которую можно увидеть на экране "Достижения".
Простые достижения
Вот пример простого достижения за получение блока земли:
java
AdvancementHolder getDirt = Advancement.Builder.advancement()
.display(
Items.DIRT, // The display icon
Component.literal("Your First Dirt Block"), // The title
Component.literal("Now make a house from it"), // The description
Identifier.withDefaultNamespace("gui/advancements/backgrounds/adventure"), // Background image for the tab in the advancements page, if this is a root advancement (has no parent)
AdvancementType.TASK, // TASK, CHALLENGE, or GOAL
true, // Show the toast when completing it
true, // Announce it to chat
false // Hide it in the advancement tab until it's achieved
)
// "got_dirt" is the name referenced by other advancements when they want to have "requirements."
.addCriterion("got_dirt", InventoryChangeTrigger.TriggerInstance.hasItems(Items.DIRT))
// Give the advancement an id
.save(consumer, Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "get_dirt"));1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Вывод JSON
json
{
"criteria": {
"got_dirt": {
"conditions": {
"items": [
{
"items": "minecraft:dirt"
}
]
},
"trigger": "minecraft:inventory_changed"
}
},
"display": {
"background": "minecraft:gui/advancements/backgrounds/adventure",
"description": "Now make a house from it",
"icon": {
"id": "minecraft:dirt"
},
"title": "Your First Dirt Block"
},
"requirements": [
[
"got_dirt"
]
],
"sends_telemetry_event": true
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Родительские достижения (Parents)
Чтобы создать или расширить дерево достижений, мы можем указать родителя для нашего достижения. Для этого вызовите метод Advancement.Builder#parent(...) и передайте ссылку на родительское достижение:
java
Advancement.Builder.advancement()
.parent(getDirt)
// ...1
2
3
2
3
Если прямой ссылки на родительское достижения нет (например, в качестве родителя используется ванильное достижение), можно создать плейсхолдер с помощью static метода класса Identifier.
java
Advancement.Builder.advancement()
.parent(createPlaceholder(Identifier.withDefaultNamespace("adventure/root")))
// ...1
2
3
2
3
Теперь ваши достижения будут отображаться в виде древовидной структуры в меню достижений.

Множественные критерии (Multiple Criteria)
Чтобы использовать более сложные условия в наших достижениях, мы можем вызывать метод Advancement.Builder#addCriteria(...) несколько раз, добавляя дополнительные критерии:
java
Advancement.Builder.advancement()
.addCriterion("ate_apple", ConsumeItemTrigger.TriggerInstance.usedItem(itemLookup, Items.APPLE))
.addCriterion("ate_cooked_beef", ConsumeItemTrigger.TriggerInstance.usedItem(itemLookup, Items.COOKED_BEEF))
// ...1
2
3
4
2
3
4
По умолчанию для выполнения достижения должны быть выполнены все указанные критерии. Мы можем изменить это поведение, задав другую стратегию требований (AdvancementRequirements.Strategy):
java
Advancement.Builder.advancement()
.addCriterion("brew_mundane", CriteriaTriggers.BREWED_POTION.createCriterion(
new BrewedPotionTrigger.TriggerInstance(Optional.empty(), Optional.of(Potions.MUNDANE))
))
.addCriterion("brew_thick", CriteriaTriggers.BREWED_POTION.createCriterion(
new BrewedPotionTrigger.TriggerInstance(Optional.empty(), Optional.of(Potions.THICK))
))
.requirements(AdvancementRequirements.Strategy.OR)
// ...1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Награды (Rewards)
Мы можем привязывать награды к нашим достижениям, которые будут выдаваться игроку сразу после их выполнения. Для этого нужно вызвать метод Advancement.Builder#rewards(...) и передать туда награды, которые мы хотим добавить:
java
Advancement.Builder.advancement()
.rewards(AdvancementRewards.Builder.experience(10))
// ...1
2
3
2
3
Также доступны и другие типы наград:
java
Advancement.Builder.advancement()
.rewards(
new AdvancementRewards.Builder()
// Give entries from a loot table
.addLootTable(ModLootTables.ADVANCEMENT_COLLECT_NETHER_STAR)
// Make recipes available in the recipe book
.addRecipe(RecipeBuilder.getDefaultRecipeId(new ItemStackTemplate(Items.BEACON)))
// Run a .mcfunction - https://minecraft.wiki/w/Function_(Java_Edition)
.runs(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "got_nether_star"))
// Give experience points
.addExperience(200)
)
// ...1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Пользовательские критерии (Custom Criteria)
WARNING
Хотя генерация данных (datagen) может выполняться на стороне клиента, сами классы критериев (Criterion) и предикатов (Predicate) должны находиться в основном наборе исходного кода (main source set), доступном для обеих сторон, поскольку именно сервер должен отслеживать и проверять их выполнение.
Определения
Критерий (Criterion / мн. ч. Criteria) — это действие, которое может совершить игрок (или событие, которое может с ним произойти), засчитывающееся для прогресса достижения. В игре уже есть много стандартных критериев, которые находятся в пакете net.minecraft.advancements.criterion. Как правило, новый критерий нужен только в том случае, если вы добавляете в игру свою уникальную механику.
Условия (Conditions) — это параметры, которые проверяются критерием. Критерий засчитывается только в том случае, если выполнены все связанные условия. Обычно условия выражаются с помощью предикатов.
Предикат (Predicate) — это функциональный интерфейс, принимающий входное значение и возвращающий результат типа boolean(логическое значение). Например, предикат Predicate<Item> может возвращать true, если предмет является алмазом, а предикат Predicate<LivingEntity> — если существо не агрессивно по отношению к деревенским жителям.
Создание пользовательских критериев
Во-первых, нам понадобится новая механика. Давайте сообщать игроку, какой инструмент он использовал, каждый раз, когда он разбивает блок.
java
public class ExampleModDatagenAdvancement implements ModInitializer {
@Override
public void onInitialize() {
HashMap<Item, Integer> tools = new HashMap<>();
PlayerBlockBreakEvents.AFTER.register(((level, player, blockPos, blockState, blockEntity) -> {
if (player instanceof ServerPlayer serverPlayer) { // Only triggers on the server side
Item item = player.getMainHandItem().getItem();
Integer usedCount = tools.getOrDefault(item, 0);
usedCount++;
tools.put(item, usedCount);
serverPlayer.sendSystemMessage(Component.nullToEmpty("You've used \"" + item + "\" as a tool " + usedCount + " times!"));
}
}));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Обратите внимание, что этот код очень плохой. Так как HashMap не хранится в постоянном месте, она будет сбрасываться при каждом перезапуске игры. Это сделано исключительно для демонстрации критериев (Criterions). Начните игру и попробуйте!
Далее создадим наш пользовательский критерий UseToolCriterion. Для него понадобится собственный класс Conditions, поэтому мы создадим их оба сразу:
java
public class UseToolCriterion extends SimpleCriterionTrigger<UseToolCriterion.Conditions> {
@Override
public Codec<Conditions> codec() {
return Conditions.CODEC;
}
public record Conditions(Optional<ContextAwarePredicate> playerPredicate) implements SimpleCriterionTrigger.SimpleInstance {
public static Codec<UseToolCriterion.Conditions> CODEC = ContextAwarePredicate.CODEC.optionalFieldOf("player")
.xmap(Conditions::new, Conditions::player).codec();
@Override
public Optional<ContextAwarePredicate> player() {
return this.playerPredicate;
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Ух, как много всего! Давайте во всём разберёмся.
UseToolCriterion— это класс-наследникSimpleCriterionTrigger, к которому могут применяться условияConditions.- В
Conditionsесть полеplayerPredicate. Все условияConditionsдолжны содержать предикат игрока (технически —LootContextPredicate). - У
Conditionsтакже естьCODEC. ЭтотCodec- просто кодек для одного поля,playerPredicate, с дополнительными инструкциями для преобразования между ними (xmap).
INFO
Чтобы узнать больше о кодеках, см. страницу Codecs.
Нам понадобится способ проверить, выполнены ли условия. Давайте добавим вспомогательный метод в Conditions:
java
public boolean requirementsMet() {
return true; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}1
2
3
2
3
Теперь, когда у нас есть критерий и его условия, нам нужно найти способ запустить его. Добавьте метод триггера к UseToolCriterion:
java
public void trigger(ServerPlayer player) {
trigger(player, Conditions::requirementsMet);
}1
2
3
2
3
Почти готово! Далее нам нужен экземпляр нашего критерия для работы. Давайте поместим его в новый класс, названный ModCriteria.
java
public class ModCriteria {
public static final UseToolCriterion USE_TOOL = CriteriaTriggers.register(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "use_tool").toString(), new UseToolCriterion());
}1
2
3
2
3
Чтобы убедиться, что наши критерии инициализируются в нужное время, добавьте пустой метод init:
java
public static void init() {
}1
2
2
И вызовите его в инициализаторе вашего мода:
java
ModCriteria.init();1
Наконец, нам нужно запустить наши критерии. Добавьте это туда, где мы отправили сообщение игроку в основном классе мода.
java
ModCriteria.USE_TOOL.trigger(serverPlayer);1
Теперь ваш новый блестящий критерий готов к использованию! Давайте добавим его в наш провайдер:
java
AdvancementHolder breakBlockWithTool = Advancement.Builder.advancement()
.parent(getDirt)
.display(
Items.DIAMOND_SHOVEL,
Component.literal("Not a Shovel"),
Component.literal("That's not a shovel (probably)"),
null,
AdvancementType.GOAL,
true,
true,
false
)
.addCriterion("break_block_with_tool", ModCriteria.USE_TOOL.createCriterion(new UseToolCriterion.Conditions(Optional.empty())))
.save(consumer, Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "break_block_with_tool"));1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Запустите задачу datagen снова — ваше новое достижение готово к использованию!
Условия с параметрами
Всё это хорошо, но что если мы хотим выдавать достижение только после 5-кратного выполнения условия? А почему не еще один в 10 раз? Для этого нам нужно задать условию параметр. Вы можете продолжать использовать UseToolCriterion или перейти на новый ParameterizedUseToolCriterion. На практике вам нужно иметь только параметризованный, но в этом уроке мы оставим оба.
Давайте работать снизу вверх. Нам нужно проверить, выполнены ли требования, поэтому давайте отредактируем наш метод Conditions#requirementsMet:
java
public boolean requirementsMet(int totalTimes) {
return totalTimes > this.requiredTimes; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}1
2
3
2
3
requiredTimes не существует, поэтому сделайте его параметром Conditions:
java
public record Conditions(Optional<ContextAwarePredicate> playerPredicate, int requiredTimes) implements SimpleCriterionTrigger.SimpleInstance {
@Override
public Optional<ContextAwarePredicate> player() {
return this.playerPredicate;
}
public boolean requirementsMet(int totalTimes) {
return totalTimes > this.requiredTimes; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Теперь наш кодек выдает ошибку. Давайте напишем новый кодек для новых изменений:
java
public static Codec<ParameterizedUseToolCriterion.Conditions> CODEC = RecordCodecBuilder.create(instance -> instance.group(
ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Conditions::player),
Codec.INT.fieldOf("requiredTimes").forGetter(Conditions::requiredTimes)
).apply(instance, Conditions::new));1
2
3
4
2
3
4
Двигаемся дальше, теперь нам нужно исправить наш метод trigger:
java
public void trigger(ServerPlayer player, int totalTimes) {
this.trigger(player, conditions -> conditions.requirementsMet(totalTimes));
}1
2
3
2
3
Если вы создали новый критерий, нам нужно добавить его в ModCriteria
java
public static final ParameterizedUseToolCriterion PARAMETERIZED_USE_TOOL = CriteriaTriggers.register(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "parameterized_use_tool").toString(), new ParameterizedUseToolCriterion());1
И вызываем его в нашем основном классе, прямо там, где был старый вариант:
java
ModCriteria.PARAMETERIZED_USE_TOOL.trigger(serverPlayer, usedCount);1
Добавьте достижение в ваш provider:
java
AdvancementHolder breakBlockWithToolFiveTimes = Advancement.Builder.advancement()
.parent(breakBlockWithTool)
.display(
Items.GOLDEN_SHOVEL,
Component.literal("Not a Shovel Still"),
Component.literal("That's still not a shovel (probably)"),
null,
AdvancementType.GOAL,
true,
true,
false
)
.addCriterion("break_block_with_tool_five_times", ModCriteria.PARAMETERIZED_USE_TOOL.createCriterion(new ParameterizedUseToolCriterion.Conditions(Optional.empty(), 5)))
.save(consumer, Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "break_block_with_tool_five_times"));1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Запустите datagen еще раз, и вы наконец-то закончили!
Условная загрузка ресурсов
Чтобы применить условную загрузку ресурсов к динамически генерируемому достижению, оберните объект-потребитель (consumer) методом withConditions и укажите любые условия ресурсов, которые вы хотите применить. В результате будет создано достижение с примененными условиями ресурсов:
java
Advancement.Builder.advancement()
.display(
ModBlocks.DUPLICATOR_BLOCK,
Component.literal("Experimental Duplication"),
Component.literal("Place a duplicator block with the Redstone Experiments flag enabled."),
null,
AdvancementType.CHALLENGE,
false, false, false)
.addCriterion("place_block", ItemUsedOnLocationTrigger.TriggerInstance.placedBlock(ModBlocks.DUPLICATOR_BLOCK))
.save(withConditions(consumer,
ResourceConditions.featuresEnabled(FeatureFlags.REDSTONE_EXPERIMENTS)), Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "experimental_duplication"));1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11





