2025-11-24 22:52:51 +03:00

378 lines
14 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.google.common.annotations.VisibleForTesting
* it.unimi.dsi.fastutil.ints.IntArrayList
* it.unimi.dsi.fastutil.ints.IntList
* it.unimi.dsi.fastutil.objects.ObjectIterable
* it.unimi.dsi.fastutil.objects.Reference2IntMap$Entry
* it.unimi.dsi.fastutil.objects.Reference2IntMaps
* it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap
* org.jspecify.annotations.Nullable
*/
package net.minecraft.world.entity.player;
import com.google.common.annotations.VisibleForTesting;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.ObjectIterable;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMaps;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.jspecify.annotations.Nullable;
public class StackedContents<T> {
public final Reference2IntOpenHashMap<T> amounts = new Reference2IntOpenHashMap();
private boolean hasAtLeast(T item, int count) {
return this.amounts.getInt(item) >= count;
}
private void take(T item, int amount) {
int previous = this.amounts.addTo(item, -amount);
if (previous < amount) {
throw new IllegalStateException("Took " + amount + " items, but only had " + previous);
}
}
private void put(T item, int count) {
this.amounts.addTo(item, count);
}
public boolean tryPick(List<? extends IngredientInfo<T>> ingredients, int amount, @Nullable Output<T> output) {
return new RecipePicker(ingredients).tryPick(amount, output);
}
public int tryPickAll(List<? extends IngredientInfo<T>> ingredients, int maxSize, @Nullable Output<T> output) {
return new RecipePicker(ingredients).tryPickAll(maxSize, output);
}
public void clear() {
this.amounts.clear();
}
public void account(T item, int count) {
this.put(item, count);
}
private List<T> getUniqueAvailableIngredientItems(Iterable<? extends IngredientInfo<T>> ingredients) {
ArrayList<Object> result = new ArrayList<Object>();
for (Reference2IntMap.Entry availableItem : Reference2IntMaps.fastIterable(this.amounts)) {
if (availableItem.getIntValue() <= 0 || !StackedContents.anyIngredientMatches(ingredients, availableItem.getKey())) continue;
result.add(availableItem.getKey());
}
return result;
}
private static <T> boolean anyIngredientMatches(Iterable<? extends IngredientInfo<T>> ingredients, T item) {
for (IngredientInfo<T> ingredient : ingredients) {
if (!ingredient.acceptsItem(item)) continue;
return true;
}
return false;
}
@VisibleForTesting
public int getResultUpperBound(List<? extends IngredientInfo<T>> ingredients) {
int min = Integer.MAX_VALUE;
ObjectIterable availableItems = Reference2IntMaps.fastIterable(this.amounts);
block0: for (IngredientInfo<Object> ingredientInfo : ingredients) {
int max = 0;
for (Reference2IntMap.Entry entry : availableItems) {
int itemCount = entry.getIntValue();
if (itemCount <= max) continue;
if (ingredientInfo.acceptsItem(entry.getKey())) {
max = itemCount;
}
if (max < min) continue;
continue block0;
}
min = max;
if (min != 0) continue;
break;
}
return min;
}
private class RecipePicker {
private final List<? extends IngredientInfo<T>> ingredients;
private final int ingredientCount;
private final List<T> items;
private final int itemCount;
private final BitSet data;
private final IntList path = new IntArrayList();
public RecipePicker(List<? extends IngredientInfo<T>> ingredients) {
this.ingredients = ingredients;
this.ingredientCount = ingredients.size();
this.items = StackedContents.this.getUniqueAvailableIngredientItems(ingredients);
this.itemCount = this.items.size();
this.data = new BitSet(this.visitedIngredientCount() + this.visitedItemCount() + this.satisfiedCount() + this.connectionCount() + this.residualCount());
this.setInitialConnections();
}
private void setInitialConnections() {
for (int ingredient = 0; ingredient < this.ingredientCount; ++ingredient) {
IngredientInfo ingredientInfo = this.ingredients.get(ingredient);
for (int item = 0; item < this.itemCount; ++item) {
if (!ingredientInfo.acceptsItem(this.items.get(item))) continue;
this.setConnection(item, ingredient);
}
}
}
public boolean tryPick(int capacity, @Nullable Output<T> output) {
IntList path;
if (capacity <= 0) {
return true;
}
int satisfiedIngredientCount = 0;
while ((path = this.tryAssigningNewItem(capacity)) != null) {
int assignedItem = path.getInt(0);
StackedContents.this.take(this.items.get(assignedItem), capacity);
int satisfiedIngredient = path.size() - 1;
this.setSatisfied(path.getInt(satisfiedIngredient));
++satisfiedIngredientCount;
for (int i = 0; i < path.size() - 1; ++i) {
int ingredient;
int item;
if (RecipePicker.isPathIndexItem(i)) {
item = path.getInt(i);
ingredient = path.getInt(i + 1);
this.assign(item, ingredient);
continue;
}
item = path.getInt(i + 1);
ingredient = path.getInt(i);
this.unassign(item, ingredient);
}
}
boolean isValidAssignment = satisfiedIngredientCount == this.ingredientCount;
boolean hasOutput = isValidAssignment && output != null;
this.clearAllVisited();
this.clearSatisfied();
block2: for (int ingredient = 0; ingredient < this.ingredientCount; ++ingredient) {
for (int item = 0; item < this.itemCount; ++item) {
if (!this.isAssigned(item, ingredient)) continue;
this.unassign(item, ingredient);
StackedContents.this.put(this.items.get(item), capacity);
if (!hasOutput) continue block2;
output.accept(this.items.get(item));
continue block2;
}
}
assert (this.data.get(this.residualOffset(), this.residualOffset() + this.residualCount()).isEmpty());
return isValidAssignment;
}
private static boolean isPathIndexItem(int index) {
return (index & 1) == 0;
}
private @Nullable IntList tryAssigningNewItem(int capacity) {
this.clearAllVisited();
for (int item = 0; item < this.itemCount; ++item) {
IntList path;
if (!StackedContents.this.hasAtLeast(this.items.get(item), capacity) || (path = this.findNewItemAssignmentPath(item)) == null) continue;
return path;
}
return null;
}
private @Nullable IntList findNewItemAssignmentPath(int startingItem) {
this.path.clear();
this.visitItem(startingItem);
this.path.add(startingItem);
while (!this.path.isEmpty()) {
int newLength;
int pathLength = this.path.size();
if (RecipePicker.isPathIndexItem(pathLength - 1)) {
int itemToAssign = this.path.getInt(pathLength - 1);
for (int ingredient = 0; ingredient < this.ingredientCount; ++ingredient) {
if (this.hasVisitedIngredient(ingredient) || !this.hasConnection(itemToAssign, ingredient) || this.isAssigned(itemToAssign, ingredient)) continue;
this.visitIngredient(ingredient);
this.path.add(ingredient);
break;
}
} else {
int lastAssignedIngredient = this.path.getInt(pathLength - 1);
if (!this.isSatisfied(lastAssignedIngredient)) {
return this.path;
}
for (int item = 0; item < this.itemCount; ++item) {
if (this.hasVisitedItem(item) || !this.isAssigned(item, lastAssignedIngredient)) continue;
assert (this.hasConnection(item, lastAssignedIngredient));
this.visitItem(item);
this.path.add(item);
break;
}
}
if ((newLength = this.path.size()) != pathLength) continue;
this.path.removeInt(newLength - 1);
}
return null;
}
private int visitedIngredientOffset() {
return 0;
}
private int visitedIngredientCount() {
return this.ingredientCount;
}
private int visitedItemOffset() {
return this.visitedIngredientOffset() + this.visitedIngredientCount();
}
private int visitedItemCount() {
return this.itemCount;
}
private int satisfiedOffset() {
return this.visitedItemOffset() + this.visitedItemCount();
}
private int satisfiedCount() {
return this.ingredientCount;
}
private int connectionOffset() {
return this.satisfiedOffset() + this.satisfiedCount();
}
private int connectionCount() {
return this.ingredientCount * this.itemCount;
}
private int residualOffset() {
return this.connectionOffset() + this.connectionCount();
}
private int residualCount() {
return this.ingredientCount * this.itemCount;
}
private boolean isSatisfied(int ingredient) {
return this.data.get(this.getSatisfiedIndex(ingredient));
}
private void setSatisfied(int ingredient) {
this.data.set(this.getSatisfiedIndex(ingredient));
}
private int getSatisfiedIndex(int ingredient) {
assert (ingredient >= 0 && ingredient < this.ingredientCount);
return this.satisfiedOffset() + ingredient;
}
private void clearSatisfied() {
this.clearRange(this.satisfiedOffset(), this.satisfiedCount());
}
private void setConnection(int item, int ingredient) {
this.data.set(this.getConnectionIndex(item, ingredient));
}
private boolean hasConnection(int item, int ingredient) {
return this.data.get(this.getConnectionIndex(item, ingredient));
}
private int getConnectionIndex(int item, int ingredient) {
assert (item >= 0 && item < this.itemCount);
assert (ingredient >= 0 && ingredient < this.ingredientCount);
return this.connectionOffset() + item * this.ingredientCount + ingredient;
}
private boolean isAssigned(int item, int ingredient) {
return this.data.get(this.getResidualIndex(item, ingredient));
}
private void assign(int item, int ingredient) {
int residualIndex = this.getResidualIndex(item, ingredient);
assert (!this.data.get(residualIndex));
this.data.set(residualIndex);
}
private void unassign(int item, int ingredient) {
int residualIndex = this.getResidualIndex(item, ingredient);
assert (this.data.get(residualIndex));
this.data.clear(residualIndex);
}
private int getResidualIndex(int item, int ingredient) {
assert (item >= 0 && item < this.itemCount);
assert (ingredient >= 0 && ingredient < this.ingredientCount);
return this.residualOffset() + item * this.ingredientCount + ingredient;
}
private void visitIngredient(int item) {
this.data.set(this.getVisitedIngredientIndex(item));
}
private boolean hasVisitedIngredient(int ingredient) {
return this.data.get(this.getVisitedIngredientIndex(ingredient));
}
private int getVisitedIngredientIndex(int ingredient) {
assert (ingredient >= 0 && ingredient < this.ingredientCount);
return this.visitedIngredientOffset() + ingredient;
}
private void visitItem(int item) {
this.data.set(this.getVisitiedItemIndex(item));
}
private boolean hasVisitedItem(int item) {
return this.data.get(this.getVisitiedItemIndex(item));
}
private int getVisitiedItemIndex(int item) {
assert (item >= 0 && item < this.itemCount);
return this.visitedItemOffset() + item;
}
private void clearAllVisited() {
this.clearRange(this.visitedIngredientOffset(), this.visitedIngredientCount());
this.clearRange(this.visitedItemOffset(), this.visitedItemCount());
}
private void clearRange(int offset, int count) {
this.data.clear(offset, offset + count);
}
public int tryPickAll(int maxSize, @Nullable Output<T> output) {
int mid;
int min = 0;
int max = Math.min(maxSize, StackedContents.this.getResultUpperBound(this.ingredients)) + 1;
while (true) {
if (this.tryPick(mid = (min + max) / 2, null)) {
if (max - min <= 1) break;
min = mid;
continue;
}
max = mid;
}
if (mid > 0) {
this.tryPick(mid, output);
}
return mid;
}
}
@FunctionalInterface
public static interface Output<T> {
public void accept(T var1);
}
@FunctionalInterface
public static interface IngredientInfo<T> {
public boolean acceptsItem(T var1);
}
}