Geração de Progresso 26.2
Um guia para configurar geração de progresso com datagen.
PRÉ-REQUISITOS
Tenha certeza que você completou o processo de configuração do datagen primeiro.
Configuração
Primeiro, nós precisamos criar o nosso provedor. Crie uma classe que estende FabricAdvancementProvider e preencha os métodos base:
java
public class ExampleModAdvancementProvider extends FabricAdvancementProvider {
public 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
Para finalizar a configuração, adicione esse provedor ao seu DataGeneratorEntrypoint dentro do método onInitializeDataGenerator.
java
pack.addProvider(ExampleModAdvancementProvider::new);1
Estrutura das Conquistas
Uma conquista é feita de alguns componentes variados. Juntamente aos requisitos, chamado "criterion" (critérios), ele pode ter:
- Algum
DisplayInfoque diz ao jogo como mostrar a conquista aos jogadores, AdvancementRequirements, cujo é a lista das listas de critério, exigindo no mínimo um critério de cada sub-lista para ser completado,AdvancementRewardsque o jogador recebe por completar a conquista.- O
Strategyque diz à conquista como lidar com múltiplos critérios e - Um
Advancementmãe, que organiza a hierarquia que você vê na tela de "Advancements".
Conquistas Simples
Aqui temos uma conquista simples por pegar um bloco de terra:
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, ExampleMod.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
Saída de 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
Classes Mães
Para criar ou estender uma árvore de conquistas, nós podemos configurar uma classe mãe para a nossa conquista. Para fazer isso, chame Advancement.Builder#parent(...) e passe uma referência para a conquista mãe.
java
Advancement.Builder.advancement()
.parent(getDirt)
// ...1
2
3
2
3
Caso não haja nenhuma referência direta disponível para a conquista mãe (por exemplo, usando uma conquista vanilla como uma conquista mãe), um espaço reservado pode ser criado usando um identificador.
java
Advancement.Builder.advancement()
.parent(createPlaceholder(Identifier.withDefaultNamespace("adventure/root")))
// ...1
2
3
2
3
Agora as suas conquistas devem ser mostradas como uma árvore no menu de conquistas.

Critérios Múltiplos
Para ter mais condições de conquistas nas nossas conquistas, nós podemos chamar Advancement.Builder#addCriteria(...) mais de uma vez com critérios adicionais.
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
Por padrão, todos os critérios devem ser atendidos para completar a conquista. Nós podemos mudar esse comportamento fornecendo uma estratégia diferente.
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
Recompensas
Nós podemos anexar recompensas às nossas conquistas, que serão concedidas quando um jogador completa a conquista. Nós podemos fazer isso chamando Advancement.Builder#rewards(...) com as recompensas que queremos adicionar.
java
Advancement.Builder.advancement()
.rewards(AdvancementRewards.Builder.experience(10))
// ...1
2
3
2
3
Há múltiplos tipos de recompensas disponíveis:
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(ExampleMod.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
Critério Personalizado
WARNING
Enquanto a datagen pode estar do lado do cliente, os Criterions e Predicates estão no conjunto de fonte principal (ambos os lados), já que o servidor precisa acioná-los e avaliá-los.
Definições
Um critério (em inglês criterion, para plural criteria) é algo que o jogador pode fazer (ou pode acontecer com o jogador) que pode ser contado para o progresso. O jogo vem com vários critérios, cujo podem ser encontrados no pacote net.minecraft.advancements.criterion. Geralmente, você precisa apenas de um novo critério se acrescentar uma mecânica personalizada ao jogo.
Condições são avaliadas pelos critérios. Um critério só é contado se todas as condições relevantes são atendidas. Condições são normalmente expressas com um predicate.
Um predicate (predicado, em português) é algo que toma um valor e retorna uma boolean. Por exemplo, o Predicate<Item> pode retornar true se o item é um diamante, enquanto o Predicate<LivingEntity> pode retornar true se a entidade não é hostil com aldeões.
Criando Critérios Personalizados
Primeiro, precisamos de uma nova mecânica para implementar. Vamos dizer ao jogador qual ferramenta ele usou toda vez que ele quebra um bloco.
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
Note que esse código é muito ruim. O HashMap não está armazenado em nenhum lugar persistente, então ele irá reniciar toda vez que o jogo é reniciado. É apenas para mostrar os Criterions. Comece o jogo e teste!
A seguir, vamos criar nosso critério personalizado, UseToolCriterion. Ele vai precisar sua própria classe Conditions para acompanhar, então nós vamos fazer as duas de uma vez:
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
Ufa, é muita coisa! Vamos por partes.
UseToolCriterioné umSimpleCriterionTrigger, cujo pode se aplicarConditions.Conditionstêm um campoplayerPredicate. Todas asConditionsdevem ter um predicado de jogador (tecnicamente umLootContextPredicate).Conditionstambém possuem umCODEC. EsseCodecé simplesmente o codec para esse único campoplayerPredicate, com instruções extras para converter entre eles (xmap).
INFO
Para aprender mais sobre codecs, veja a página Codecs.
Nós vamos precisar de uma forma de verificar se as condições foram atendidas. Vamos adicionar um método ajudante em Conditions:
java
public boolean requirementsMet() {
return true; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}1
2
3
2
3
Agora que nós temos um critério e suas condições, nós vamos precisar de um jeito de ativá-lo. Adicione um método gatilho para UseToolCriterion:
java
public void trigger(ServerPlayer player) {
trigger(player, Conditions::requirementsMet);
}1
2
3
2
3
Quase lá! A seguir, nós vamos precisar de uma instância do nosso critério para trabalhar. Vamos colocar ela em uma nova classe, chamada ModCriteria, com um método ajudante para registrar facilmente o novo critério.
java
public class ModCriteria {
public static final UseToolCriterion USE_TOOL = register("use_tool", new UseToolCriterion());
private static <T extends CriterionTrigger<?>> T register(final String name, final T criterion) {
return Registry.register(BuiltInRegistries.TRIGGER_TYPES, ExampleMod.id(name), criterion);
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Para ter certeza que nossos critérios foram iniciados no tempo certo, adicione um método init vazio:
java
public static void init() {
}1
2
2
E você pode chamar no seu iniciador de mod:
java
ModCriteria.init();1
Finalmente nós podemos ativar nossos critérios. Adicione isso no lugar que nós enviamos a mensagem para o jogador na classe principal do mod.
java
ModCriteria.USE_TOOL.trigger(serverPlayer);1
Seu critério novinho em folha está pronto para ser usado! Vamos adicioná-lo ao nosso provedor:
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, ExampleMod.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
Execute a tarefa datagen novamente, e agora você tem sua nova conquista pronta!
Condições com Parâmetros
Está tudo bem e tal, mas se por acaso nós quisermos garantir uma conquista somente quando algo for feito 5 vezes? E por que não outro após 10 vezes? Para isso, nós precisamos dar um parametro para a nossa condição. Você pode continuar com UseToolCriterion, ou você pode seguir com o novo ParameterizedUseToolCriterion. Na prática, você deve apenas ter o parâmetrizado, mas nós vamos manter ambos para esse tutorial.
Vamos começar de baixo para cima. Nós precisaremos verificar se os requisitos foram atendidos, então vamos editar o nosso método 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 não existe, então vamos transformá-lo em um parâmetro de 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
Agora nosso codec está falhando. Vamos escrever um novo codec para as novas mudanças:
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
Seguindo em frente, agora precisamos consertar o método trigger:
java
public void trigger(ServerPlayer player, int totalTimes) {
this.trigger(player, conditions -> conditions.requirementsMet(totalTimes));
}1
2
3
2
3
Se você fez um novo critério, nós precisamos adicioná-lo ao ModCriteria
java
public static final ParameterizedUseToolCriterion PARAMETERIZED_USE_TOOL = register("parameterized_use_tool", new ParameterizedUseToolCriterion());1
E precisamos chamar na classe principal, onde estava a antiga:
java
ModCriteria.PARAMETERIZED_USE_TOOL.trigger(serverPlayer, usedCount);1
Adicione a conquista ao seu provedor:
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, ExampleMod.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
Execute o datagen novamente e finalmente está pronto!
Condições de Recurso
Para aplicar uma condição de recurso a uma conquista gerada por dados, envolva o consumer com withConditions e forneça qualquer condição de recurso que você deseja aplicar. Isso gerará então uma conquista que possui condições de recurso aplicadas:
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)), ExampleMod.id("experimental_duplication"));1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11






