minecraft-src/net/minecraft/advancements/Advancement.java
2025-07-04 01:41:11 +03:00

215 lines
8.5 KiB
Java

package net.minecraft.advancements;
import com.google.common.collect.ImmutableMap;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import net.minecraft.ChatFormatting;
import net.minecraft.advancements.critereon.CriterionValidator;
import net.minecraft.core.HolderGetter;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.Nullable;
public record Advancement(
Optional<ResourceLocation> parent,
Optional<DisplayInfo> display,
AdvancementRewards rewards,
Map<String, Criterion<?>> criteria,
AdvancementRequirements requirements,
boolean sendsTelemetryEvent,
Optional<Component> name
) {
private static final Codec<Map<String, Criterion<?>>> CRITERIA_CODEC = Codec.unboundedMap(Codec.STRING, Criterion.CODEC)
.validate(map -> map.isEmpty() ? DataResult.error(() -> "Advancement criteria cannot be empty") : DataResult.success(map));
public static final Codec<Advancement> CODEC = RecordCodecBuilder.<Advancement>create(
instance -> instance.group(
ResourceLocation.CODEC.optionalFieldOf("parent").forGetter(Advancement::parent),
DisplayInfo.CODEC.optionalFieldOf("display").forGetter(Advancement::display),
AdvancementRewards.CODEC.optionalFieldOf("rewards", AdvancementRewards.EMPTY).forGetter(Advancement::rewards),
CRITERIA_CODEC.fieldOf("criteria").forGetter(Advancement::criteria),
AdvancementRequirements.CODEC.optionalFieldOf("requirements").forGetter(advancement -> Optional.of(advancement.requirements())),
Codec.BOOL.optionalFieldOf("sends_telemetry_event", false).forGetter(Advancement::sendsTelemetryEvent)
)
.apply(instance, (optional, optional2, advancementRewards, map, optional3, boolean_) -> {
AdvancementRequirements advancementRequirements = (AdvancementRequirements)optional3.orElseGet(() -> AdvancementRequirements.allOf(map.keySet()));
return new Advancement(optional, optional2, advancementRewards, map, advancementRequirements, boolean_);
})
)
.validate(Advancement::validate);
public static final StreamCodec<RegistryFriendlyByteBuf, Advancement> STREAM_CODEC = StreamCodec.ofMember(Advancement::write, Advancement::read);
public Advancement(
Optional<ResourceLocation> parent,
Optional<DisplayInfo> display,
AdvancementRewards rewards,
Map<String, Criterion<?>> criteria,
AdvancementRequirements requirements,
boolean sendsTelemetryEvent
) {
this(parent, display, rewards, Map.copyOf(criteria), requirements, sendsTelemetryEvent, display.map(Advancement::decorateName));
}
private static DataResult<Advancement> validate(Advancement advancement) {
return advancement.requirements().validate(advancement.criteria().keySet()).map(advancementRequirements -> advancement);
}
private static Component decorateName(DisplayInfo display) {
Component component = display.getTitle();
ChatFormatting chatFormatting = display.getType().getChatColor();
Component component2 = ComponentUtils.mergeStyles(component.copy(), Style.EMPTY.withColor(chatFormatting)).append("\n").append(display.getDescription());
Component component3 = component.copy().withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, component2)));
return ComponentUtils.wrapInSquareBrackets(component3).withStyle(chatFormatting);
}
public static Component name(AdvancementHolder advancement) {
return (Component)advancement.value().name().orElseGet(() -> Component.literal(advancement.id().toString()));
}
private void write(RegistryFriendlyByteBuf buffer) {
buffer.writeOptional(this.parent, FriendlyByteBuf::writeResourceLocation);
DisplayInfo.STREAM_CODEC.apply(ByteBufCodecs::optional).encode(buffer, this.display);
this.requirements.write(buffer);
buffer.writeBoolean(this.sendsTelemetryEvent);
}
private static Advancement read(RegistryFriendlyByteBuf buffer) {
return new Advancement(
buffer.readOptional(FriendlyByteBuf::readResourceLocation),
(Optional<DisplayInfo>)DisplayInfo.STREAM_CODEC.apply(ByteBufCodecs::optional).decode(buffer),
AdvancementRewards.EMPTY,
Map.of(),
new AdvancementRequirements(buffer),
buffer.readBoolean()
);
}
public boolean isRoot() {
return this.parent.isEmpty();
}
public void validate(ProblemReporter reporter, HolderGetter.Provider lootData) {
this.criteria.forEach((string, criterion) -> {
CriterionValidator criterionValidator = new CriterionValidator(reporter.forChild(string), lootData);
criterion.triggerInstance().validate(criterionValidator);
});
}
public static class Builder {
private Optional<ResourceLocation> parent = Optional.empty();
private Optional<DisplayInfo> display = Optional.empty();
private AdvancementRewards rewards = AdvancementRewards.EMPTY;
private final ImmutableMap.Builder<String, Criterion<?>> criteria = ImmutableMap.builder();
private Optional<AdvancementRequirements> requirements = Optional.empty();
private AdvancementRequirements.Strategy requirementsStrategy = AdvancementRequirements.Strategy.AND;
private boolean sendsTelemetryEvent;
public static Advancement.Builder advancement() {
return new Advancement.Builder().sendsTelemetryEvent();
}
public static Advancement.Builder recipeAdvancement() {
return new Advancement.Builder();
}
public Advancement.Builder parent(AdvancementHolder parent) {
this.parent = Optional.of(parent.id());
return this;
}
@Deprecated(
forRemoval = true
)
public Advancement.Builder parent(ResourceLocation parentId) {
this.parent = Optional.of(parentId);
return this;
}
public Advancement.Builder display(
ItemStack icon,
Component title,
Component description,
@Nullable ResourceLocation background,
AdvancementType type,
boolean showToast,
boolean announceChat,
boolean hidden
) {
return this.display(new DisplayInfo(icon, title, description, Optional.ofNullable(background), type, showToast, announceChat, hidden));
}
public Advancement.Builder display(
ItemLike icon,
Component title,
Component description,
@Nullable ResourceLocation background,
AdvancementType type,
boolean showToast,
boolean announceChat,
boolean hidden
) {
return this.display(
new DisplayInfo(new ItemStack(icon.asItem()), title, description, Optional.ofNullable(background), type, showToast, announceChat, hidden)
);
}
public Advancement.Builder display(DisplayInfo display) {
this.display = Optional.of(display);
return this;
}
public Advancement.Builder rewards(AdvancementRewards.Builder rewardsBuilder) {
return this.rewards(rewardsBuilder.build());
}
public Advancement.Builder rewards(AdvancementRewards rewards) {
this.rewards = rewards;
return this;
}
public Advancement.Builder addCriterion(String key, Criterion<?> criterion) {
this.criteria.put(key, criterion);
return this;
}
public Advancement.Builder requirements(AdvancementRequirements.Strategy requirementsStrategy) {
this.requirementsStrategy = requirementsStrategy;
return this;
}
public Advancement.Builder requirements(AdvancementRequirements requirements) {
this.requirements = Optional.of(requirements);
return this;
}
public Advancement.Builder sendsTelemetryEvent() {
this.sendsTelemetryEvent = true;
return this;
}
public AdvancementHolder build(ResourceLocation id) {
Map<String, Criterion<?>> map = this.criteria.buildOrThrow();
AdvancementRequirements advancementRequirements = (AdvancementRequirements)this.requirements.orElseGet(() -> this.requirementsStrategy.create(map.keySet()));
return new AdvancementHolder(id, new Advancement(this.parent, this.display, this.rewards, map, advancementRequirements, this.sendsTelemetryEvent));
}
public AdvancementHolder save(Consumer<AdvancementHolder> output, String id) {
AdvancementHolder advancementHolder = this.build(ResourceLocation.parse(id));
output.accept(advancementHolder);
return advancementHolder;
}
}
}