minecraft-src/net/minecraft/world/level/storage/loot/LootTable.java
2025-07-04 03:45:38 +03:00

269 lines
9.8 KiB
Java

package net.minecraft.world.level.storage.loot;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.RegistryFileCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.context.ContextKeySet;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.loot.functions.FunctionUserBuilder;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctions;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import org.slf4j.Logger;
public class LootTable {
private static final Logger LOGGER = LogUtils.getLogger();
public static final Codec<ResourceKey<LootTable>> KEY_CODEC = ResourceKey.codec(Registries.LOOT_TABLE);
public static final ContextKeySet DEFAULT_PARAM_SET = LootContextParamSets.ALL_PARAMS;
public static final long RANDOMIZE_SEED = 0L;
public static final Codec<LootTable> DIRECT_CODEC = Codec.lazyInitialized(
() -> RecordCodecBuilder.create(
instance -> instance.group(
LootContextParamSets.CODEC.lenientOptionalFieldOf("type", DEFAULT_PARAM_SET).forGetter(lootTable -> lootTable.paramSet),
ResourceLocation.CODEC.optionalFieldOf("random_sequence").forGetter(lootTable -> lootTable.randomSequence),
LootPool.CODEC.listOf().optionalFieldOf("pools", List.of()).forGetter(lootTable -> lootTable.pools),
LootItemFunctions.ROOT_CODEC.listOf().optionalFieldOf("functions", List.of()).forGetter(lootTable -> lootTable.functions)
)
.apply(instance, LootTable::new)
)
);
public static final Codec<Holder<LootTable>> CODEC = RegistryFileCodec.create(Registries.LOOT_TABLE, DIRECT_CODEC);
public static final LootTable EMPTY = new LootTable(LootContextParamSets.EMPTY, Optional.empty(), List.of(), List.of());
private final ContextKeySet paramSet;
private final Optional<ResourceLocation> randomSequence;
private final List<LootPool> pools;
private final List<LootItemFunction> functions;
private final BiFunction<ItemStack, LootContext, ItemStack> compositeFunction;
LootTable(ContextKeySet paramSet, Optional<ResourceLocation> randomSequence, List<LootPool> pools, List<LootItemFunction> functions) {
this.paramSet = paramSet;
this.randomSequence = randomSequence;
this.pools = pools;
this.functions = functions;
this.compositeFunction = LootItemFunctions.compose(functions);
}
public static Consumer<ItemStack> createStackSplitter(ServerLevel level, Consumer<ItemStack> output) {
return itemStack -> {
if (itemStack.isItemEnabled(level.enabledFeatures())) {
if (itemStack.getCount() < itemStack.getMaxStackSize()) {
output.accept(itemStack);
} else {
int i = itemStack.getCount();
while (i > 0) {
ItemStack itemStack2 = itemStack.copyWithCount(Math.min(itemStack.getMaxStackSize(), i));
i -= itemStack2.getCount();
output.accept(itemStack2);
}
}
}
};
}
public void getRandomItemsRaw(LootParams params, Consumer<ItemStack> output) {
this.getRandomItemsRaw(new LootContext.Builder(params).create(this.randomSequence), output);
}
/**
* Generate items to the given Consumer, ignoring maximum stack size.
*/
public void getRandomItemsRaw(LootContext context, Consumer<ItemStack> output) {
LootContext.VisitedEntry<?> visitedEntry = LootContext.createVisitedEntry(this);
if (context.pushVisitedElement(visitedEntry)) {
Consumer<ItemStack> consumer = LootItemFunction.decorate(this.compositeFunction, output, context);
for (LootPool lootPool : this.pools) {
lootPool.addRandomItems(consumer, context);
}
context.popVisitedElement(visitedEntry);
} else {
LOGGER.warn("Detected infinite loop in loot tables");
}
}
public void getRandomItems(LootParams params, long seed, Consumer<ItemStack> output) {
this.getRandomItemsRaw(
new LootContext.Builder(params).withOptionalRandomSeed(seed).create(this.randomSequence), createStackSplitter(params.getLevel(), output)
);
}
public void getRandomItems(LootParams params, Consumer<ItemStack> output) {
this.getRandomItemsRaw(params, createStackSplitter(params.getLevel(), output));
}
/**
* Generate random items to the given Consumer, ensuring they do not exceed their maximum stack size.
*/
public void getRandomItems(LootContext contextData, Consumer<ItemStack> output) {
this.getRandomItemsRaw(contextData, createStackSplitter(contextData.getLevel(), output));
}
public ObjectArrayList<ItemStack> getRandomItems(LootParams params, RandomSource random) {
return this.getRandomItems(new LootContext.Builder(params).withOptionalRandomSource(random).create(this.randomSequence));
}
public ObjectArrayList<ItemStack> getRandomItems(LootParams params, long seed) {
return this.getRandomItems(new LootContext.Builder(params).withOptionalRandomSeed(seed).create(this.randomSequence));
}
public ObjectArrayList<ItemStack> getRandomItems(LootParams params) {
return this.getRandomItems(new LootContext.Builder(params).create(this.randomSequence));
}
/**
* Generate random items to a List.
*/
private ObjectArrayList<ItemStack> getRandomItems(LootContext context) {
ObjectArrayList<ItemStack> objectArrayList = new ObjectArrayList<>();
this.getRandomItems(context, objectArrayList::add);
return objectArrayList;
}
public ContextKeySet getParamSet() {
return this.paramSet;
}
/**
* Validate this LootTable using the given ValidationContext.
*/
public void validate(ValidationContext validator) {
for (int i = 0; i < this.pools.size(); i++) {
((LootPool)this.pools.get(i)).validate(validator.forChild(".pools[" + i + "]"));
}
for (int i = 0; i < this.functions.size(); i++) {
((LootItemFunction)this.functions.get(i)).validate(validator.forChild(".functions[" + i + "]"));
}
}
public void fill(Container container, LootParams params, long seed) {
LootContext lootContext = new LootContext.Builder(params).withOptionalRandomSeed(seed).create(this.randomSequence);
ObjectArrayList<ItemStack> objectArrayList = this.getRandomItems(lootContext);
RandomSource randomSource = lootContext.getRandom();
List<Integer> list = this.getAvailableSlots(container, randomSource);
this.shuffleAndSplitItems(objectArrayList, list.size(), randomSource);
for (ItemStack itemStack : objectArrayList) {
if (list.isEmpty()) {
LOGGER.warn("Tried to over-fill a container");
return;
}
if (itemStack.isEmpty()) {
container.setItem((Integer)list.remove(list.size() - 1), ItemStack.EMPTY);
} else {
container.setItem((Integer)list.remove(list.size() - 1), itemStack);
}
}
}
/**
* Shuffles items by changing their order and splitting stacks
*/
private void shuffleAndSplitItems(ObjectArrayList<ItemStack> stacks, int emptySlotsCount, RandomSource random) {
List<ItemStack> list = Lists.<ItemStack>newArrayList();
Iterator<ItemStack> iterator = stacks.iterator();
while (iterator.hasNext()) {
ItemStack itemStack = (ItemStack)iterator.next();
if (itemStack.isEmpty()) {
iterator.remove();
} else if (itemStack.getCount() > 1) {
list.add(itemStack);
iterator.remove();
}
}
while (emptySlotsCount - stacks.size() - list.size() > 0 && !list.isEmpty()) {
ItemStack itemStack2 = (ItemStack)list.remove(Mth.nextInt(random, 0, list.size() - 1));
int i = Mth.nextInt(random, 1, itemStack2.getCount() / 2);
ItemStack itemStack3 = itemStack2.split(i);
if (itemStack2.getCount() > 1 && random.nextBoolean()) {
list.add(itemStack2);
} else {
stacks.add(itemStack2);
}
if (itemStack3.getCount() > 1 && random.nextBoolean()) {
list.add(itemStack3);
} else {
stacks.add(itemStack3);
}
}
stacks.addAll(list);
Util.shuffle(stacks, random);
}
private List<Integer> getAvailableSlots(Container inventory, RandomSource random) {
ObjectArrayList<Integer> objectArrayList = new ObjectArrayList<>();
for (int i = 0; i < inventory.getContainerSize(); i++) {
if (inventory.getItem(i).isEmpty()) {
objectArrayList.add(i);
}
}
Util.shuffle(objectArrayList, random);
return objectArrayList;
}
public static LootTable.Builder lootTable() {
return new LootTable.Builder();
}
public static class Builder implements FunctionUserBuilder<LootTable.Builder> {
private final ImmutableList.Builder<LootPool> pools = ImmutableList.builder();
private final ImmutableList.Builder<LootItemFunction> functions = ImmutableList.builder();
private ContextKeySet paramSet = LootTable.DEFAULT_PARAM_SET;
private Optional<ResourceLocation> randomSequence = Optional.empty();
public LootTable.Builder withPool(LootPool.Builder lootPool) {
this.pools.add(lootPool.build());
return this;
}
public LootTable.Builder setParamSet(ContextKeySet paramSet) {
this.paramSet = paramSet;
return this;
}
public LootTable.Builder setRandomSequence(ResourceLocation randomSequence) {
this.randomSequence = Optional.of(randomSequence);
return this;
}
public LootTable.Builder apply(LootItemFunction.Builder builder) {
this.functions.add(builder.build());
return this;
}
public LootTable.Builder unwrap() {
return this;
}
public LootTable build() {
return new LootTable(this.paramSet, this.randomSequence, this.pools.build(), this.functions.build());
}
}
}