package net.minecraft.world.level.storage.loot; import com.google.common.collect.Sets; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import net.minecraft.core.HolderGetter; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.RandomSource; import net.minecraft.util.StringRepresentable; import net.minecraft.util.context.ContextKey; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.storage.loot.functions.LootItemFunction; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; import org.jetbrains.annotations.Nullable; /** * LootContext stores various context information for loot generation. * This includes the Level as well as any known {@link LootContextParam}s. */ public class LootContext { private final LootParams params; private final RandomSource random; private final HolderGetter.Provider lootDataResolver; private final Set> visitedElements = Sets.>newLinkedHashSet(); LootContext(LootParams params, RandomSource random, HolderGetter.Provider lootDataResolver) { this.params = params; this.random = random; this.lootDataResolver = lootDataResolver; } public boolean hasParameter(ContextKey parameter) { return this.params.contextMap().has(parameter); } public T getParameter(ContextKey parameter) { return this.params.contextMap().getOrThrow(parameter); } @Nullable public T getOptionalParameter(ContextKey parameter) { return this.params.contextMap().getOptional(parameter); } /** * Add the dynamic drops for the given dynamic drops name to the given consumer. * If no dynamic drops provider for the given name has been registered to this LootContext, nothing is generated. * * @see DynamicDrops */ public void addDynamicDrops(ResourceLocation name, Consumer consumer) { this.params.addDynamicDrops(name, consumer); } public boolean hasVisitedElement(LootContext.VisitedEntry element) { return this.visitedElements.contains(element); } public boolean pushVisitedElement(LootContext.VisitedEntry element) { return this.visitedElements.add(element); } public void popVisitedElement(LootContext.VisitedEntry element) { this.visitedElements.remove(element); } public HolderGetter.Provider getResolver() { return this.lootDataResolver; } public RandomSource getRandom() { return this.random; } /** * The luck value for this loot context. This is usually just the player's {@linkplain Attributes#LUCK luck value}, however it may be modified depending on the context of the looting. * When fishing for example it is increased based on the Luck of the Sea enchantment. */ public float getLuck() { return this.params.getLuck(); } public ServerLevel getLevel() { return this.params.getLevel(); } public static LootContext.VisitedEntry createVisitedEntry(LootTable lootTable) { return new LootContext.VisitedEntry<>(LootDataType.TABLE, lootTable); } public static LootContext.VisitedEntry createVisitedEntry(LootItemCondition predicate) { return new LootContext.VisitedEntry<>(LootDataType.PREDICATE, predicate); } public static LootContext.VisitedEntry createVisitedEntry(LootItemFunction modifier) { return new LootContext.VisitedEntry<>(LootDataType.MODIFIER, modifier); } public static class Builder { private final LootParams params; @Nullable private RandomSource random; public Builder(LootParams params) { this.params = params; } public LootContext.Builder withOptionalRandomSeed(long seed) { if (seed != 0L) { this.random = RandomSource.create(seed); } return this; } public LootContext.Builder withOptionalRandomSource(RandomSource random) { this.random = random; return this; } public ServerLevel getLevel() { return this.params.getLevel(); } public LootContext create(Optional sequence) { ServerLevel serverLevel = this.getLevel(); MinecraftServer minecraftServer = serverLevel.getServer(); RandomSource randomSource = (RandomSource)Optional.ofNullable(this.random) .or(() -> sequence.map(serverLevel::getRandomSequence)) .orElseGet(serverLevel::getRandom); return new LootContext(this.params, randomSource, minecraftServer.reloadableRegistries().lookup()); } } /** * Represents a type of entity that can be looked up in a {@link LootContext} using a {@link LootContextParam}. */ public static enum EntityTarget implements StringRepresentable { /** * Looks up {@link LootContextParams#THIS_ENTITY}. */ THIS("this", LootContextParams.THIS_ENTITY), ATTACKER("attacker", LootContextParams.ATTACKING_ENTITY), DIRECT_ATTACKER("direct_attacker", LootContextParams.DIRECT_ATTACKING_ENTITY), ATTACKING_PLAYER("attacking_player", LootContextParams.LAST_DAMAGE_PLAYER); public static final StringRepresentable.EnumCodec CODEC = StringRepresentable.fromEnum(LootContext.EntityTarget::values); private final String name; private final ContextKey param; private EntityTarget(final String name, final ContextKey param) { this.name = name; this.param = param; } public ContextKey getParam() { return this.param; } public static LootContext.EntityTarget getByName(String name) { LootContext.EntityTarget entityTarget = (LootContext.EntityTarget)CODEC.byName(name); if (entityTarget != null) { return entityTarget; } else { throw new IllegalArgumentException("Invalid entity target " + name); } } @Override public String getSerializedName() { return this.name; } } public record VisitedEntry(LootDataType type, T value) { } }