aboutsummaryrefslogtreecommitdiff
path: root/runtime-engine/entry-stacks/src/main/java/me
diff options
context:
space:
mode:
Diffstat (limited to 'runtime-engine/entry-stacks/src/main/java/me')
-rw-r--r--runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java476
-rw-r--r--runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/settings/EntrySettingsAdapterRegistryImpl.java71
-rw-r--r--runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/AbstractEntryStack.java281
-rw-r--r--runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/EmptyEntryStack.java45
-rw-r--r--runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/EntryStackProviderImpl.java50
-rw-r--r--runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/TypedEntryStack.java53
6 files changed, 976 insertions, 0 deletions
diff --git a/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java
new file mode 100644
index 000000000..4da98dd58
--- /dev/null
+++ b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java
@@ -0,0 +1,476 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.entry;
+
+import com.google.common.collect.Iterators;
+import me.shedaniel.rei.api.common.entry.EntryIngredient;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.impl.common.provider.EntryIngredientProvider;
+import net.minecraft.nbt.ListTag;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.StreamSupport;
+
+@ApiStatus.Internal
+public class EntryIngredientImpl implements EntryIngredientProvider {
+ @Override
+ public EntryIngredient empty() {
+ return EmptyEntryIngredient.EMPTY;
+ }
+
+ @Override
+ public EntryIngredient of(EntryStack<?> stack) {
+ return new SingletonEntryIngredient(stack);
+ }
+
+ @Override
+ public EntryIngredient of(EntryStack<?>... stacks) {
+ if (stacks.length == 0) return empty();
+ if (stacks.length == 1) return of(stacks[0]);
+ return _of(stacks);
+ }
+
+ @Override
+ public EntryIngredient of(Iterable<EntryStack<?>> stacks) {
+ if (stacks instanceof EntryIngredient) return (EntryIngredient) stacks;
+ if (stacks instanceof Collection<EntryStack<?>> collection) {
+ int size = collection.size();
+ if (size == 0) return empty();
+ if (size == 1) return of(stacks.iterator().next());
+ return _of(collection.toArray(new EntryStack[0]));
+ }
+ return _of(StreamSupport.stream(stacks.spliterator(), false).toArray(EntryStack[]::new));
+ }
+
+ private EntryIngredient _of(EntryStack<?>... stacks) {
+ return new ArrayIngredient(stacks);
+ }
+
+ @Override
+ public EntryIngredient.Builder builder() {
+ return new EntryIngredientBuilder(0);
+ }
+
+ @Override
+ public EntryIngredient.Builder builder(int initialCapacity) {
+ return new EntryIngredientBuilder(initialCapacity);
+ }
+
+ private static class EntryIngredientBuilder implements EntryIngredient.Builder {
+ private EntryStack<?>[] contents;
+ private int size = 0;
+
+ public EntryIngredientBuilder(int initialCapacity) {
+ this.contents = new EntryStack[initialCapacity];
+ }
+
+ private void ensureCapacity(int minCapacity) {
+ if (contents.length < minCapacity) {
+ this.contents = Arrays.copyOf(this.contents, expandedCapacity(contents.length, minCapacity));
+ }
+ }
+
+ static int expandedCapacity(int oldCapacity, int minCapacity) {
+ int newCapacity = oldCapacity + (oldCapacity >> 1) + 1;
+ if (newCapacity < minCapacity) {
+ newCapacity = Integer.highestOneBit(minCapacity - 1) << 1;
+ }
+ if (newCapacity < 0) {
+ newCapacity = Integer.MAX_VALUE;
+ }
+ return newCapacity;
+ }
+
+ @Override
+ public EntryIngredient.Builder add(EntryStack<?> stack) {
+ ensureCapacity(size + 1);
+ contents[size++] = stack;
+ return this;
+ }
+
+ @Override
+ public EntryIngredient.Builder add(EntryStack<?>... stacks) {
+ ensureCapacity(size + stacks.length);
+ System.arraycopy(stacks, 0, contents, size, stacks.length);
+ size += stacks.length;
+ return this;
+ }
+
+ @Override
+ public EntryIngredient.Builder addAll(Iterable<? extends EntryStack<?>> stacks) {
+ if (stacks instanceof Collection<?> collection) {
+ ensureCapacity(size + collection.size());
+ }
+ for (EntryStack<?> stack : stacks) {
+ add(stack);
+ }
+ return this;
+ }
+
+ @Override
+ public EntryIngredient build() {
+ if (contents.length > size) {
+ return EntryIngredient.of((EntryStack<Object>[]) Arrays.copyOf(contents, size));
+ }
+ return EntryIngredient.of((EntryStack<Object>[]) contents);
+ }
+ }
+
+ private static class EmptyEntryIngredient extends AbstractList<EntryStack<?>> implements EntryIngredient, RandomAccess {
+ private static final EmptyEntryIngredient EMPTY = new EmptyEntryIngredient();
+
+ @Override
+ public Iterator<EntryStack<?>> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public ListIterator<EntryStack<?>> listIterator() {
+ return Collections.emptyListIterator();
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public boolean contains(Object obj) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return c.isEmpty();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ if (a.length > 0)
+ a[0] = null;
+ return a;
+ }
+
+ @Override
+ public EntryStack<?> get(int index) {
+ throw new IndexOutOfBoundsException("Index: " + index);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof List) && ((List<?>) o).isEmpty();
+ }
+
+ @Override
+ public int hashCode() {
+ return 1;
+ }
+
+ @Override
+ public boolean removeIf(Predicate<? super EntryStack<?>> filter) {
+ Objects.requireNonNull(filter);
+ return false;
+ }
+
+ @Override
+ public void replaceAll(UnaryOperator<EntryStack<?>> operator) {
+ Objects.requireNonNull(operator);
+ }
+
+ @Override
+ public void sort(Comparator<? super EntryStack<?>> c) {
+ }
+
+ @Override
+ public void forEach(Consumer<? super EntryStack<?>> action) {
+ Objects.requireNonNull(action);
+ }
+
+ @Override
+ public Spliterator<EntryStack<?>> spliterator() {
+ return Spliterators.emptySpliterator();
+ }
+
+ @Override
+ public ListTag save() {
+ return new ListTag();
+ }
+
+ @Override
+ public EntryIngredient filter(Predicate<EntryStack<?>> filter) {
+ return this;
+ }
+
+ @Override
+ public EntryIngredient map(UnaryOperator<EntryStack<?>> transformer) {
+ return this;
+ }
+ }
+
+ private static class SingletonEntryIngredient extends AbstractList<EntryStack<?>> implements EntryIngredient, RandomAccess {
+ private EntryStack<?> stack;
+
+ public SingletonEntryIngredient(EntryStack<?> stack) {
+ this.stack = stack;
+ }
+
+ @Override
+ public Iterator<EntryStack<?>> iterator() {
+ return Iterators.singletonIterator(stack);
+ }
+
+ @Override
+ public int size() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object obj) {
+ return Objects.equals(obj, stack);
+ }
+
+ @Override
+ public Object[] toArray() {
+ return new Object[]{stack};
+ }
+
+ @Override
+ public EntryStack<?> get(int index) {
+ if (index != 0)
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: 1");
+ return stack;
+ }
+
+ @Override
+ public boolean removeIf(Predicate<? super EntryStack<?>> filter) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void replaceAll(UnaryOperator<EntryStack<?>> operator) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sort(Comparator<? super EntryStack<?>> c) {
+ }
+
+ @Override
+ public void forEach(Consumer<? super EntryStack<?>> action) {
+ action.accept(stack);
+ }
+
+ @Override
+ public Spliterator<EntryStack<?>> spliterator() {
+ return singletonSpliterator(stack);
+ }
+
+ static <T> Spliterator<T> singletonSpliterator(final T element) {
+ return new Spliterator<T>() {
+ long est = 1;
+
+ @Override
+ public Spliterator<T> trySplit() {
+ return null;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer<? super T> consumer) {
+ Objects.requireNonNull(consumer);
+ if (est > 0) {
+ est--;
+ consumer.accept(element);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super T> consumer) {
+ tryAdvance(consumer);
+ }
+
+ @Override
+ public long estimateSize() {
+ return est;
+ }
+
+ @Override
+ public int characteristics() {
+ int value = (element != null) ? Spliterator.NONNULL : 0;
+
+ return value | Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.IMMUTABLE |
+ Spliterator.DISTINCT | Spliterator.ORDERED;
+ }
+ };
+ }
+
+ @Override
+ public ListTag save() {
+ ListTag listTag = new ListTag();
+ listTag.add(stack.saveStack());
+ return listTag;
+ }
+
+ @Override
+ public EntryIngredient filter(Predicate<EntryStack<?>> filter) {
+ if (filter.test(stack)) {
+ return this;
+ }
+ return EmptyEntryIngredient.EMPTY;
+ }
+
+ @Override
+ public EntryIngredient map(UnaryOperator<EntryStack<?>> transformer) {
+ return new SingletonEntryIngredient(transformer.apply(stack));
+ }
+ }
+
+ private static class ArrayIngredient extends AbstractList<EntryStack<?>> implements EntryIngredient, RandomAccess {
+ private static final long serialVersionUID = -2764017481108945198L;
+ private final EntryStack<?>[] array;
+
+ ArrayIngredient(EntryStack<?>[] array) {
+ this.array = Objects.requireNonNull(array);
+ }
+
+ @Override
+ public int size() {
+ return array.length;
+ }
+
+ @Override
+ public Object[] toArray() {
+ return toArray(new Object[0]);
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ int size = size();
+ if (a.length < size) {
+ return Arrays.copyOf(this.array, size, (Class<? extends T[]>) a.getClass());
+ }
+ System.arraycopy(this.array, 0, a, 0, size);
+ if (a.length > size) {
+ a[size] = null;
+ }
+ return a;
+ }
+
+ @Override
+ public EntryStack<?> get(int index) {
+ return array[index];
+ }
+
+ @Override
+ public EntryStack<?> set(int index, EntryStack<?> element) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ EntryStack<?>[] a = this.array;
+ if (o == null) {
+ for (int i = 0; i < a.length; i++)
+ if (a[i] == null)
+ return i;
+ } else {
+ for (int i = 0; i < a.length; i++)
+ if (o.equals(a[i]))
+ return i;
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return indexOf(o) != -1;
+ }
+
+ @Override
+ public Spliterator<EntryStack<?>> spliterator() {
+ return Spliterators.spliterator(array, Spliterator.ORDERED | Spliterator.IMMUTABLE);
+ }
+
+ @Override
+ public void forEach(Consumer<? super EntryStack<?>> action) {
+ Objects.requireNonNull(action);
+ for (EntryStack<?> stack : array) {
+ action.accept(stack);
+ }
+ }
+
+ @Override
+ public void replaceAll(UnaryOperator<EntryStack<?>> operator) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sort(Comparator<? super EntryStack<?>> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ListTag save() {
+ ListTag listTag = new ListTag();
+ for (EntryStack<?> stack : array) {
+ listTag.add(stack.saveStack());
+ }
+ return listTag;
+ }
+
+ @Override
+ public EntryIngredient filter(Predicate<EntryStack<?>> filter) {
+ return EntryIngredient.of(stream().filter(filter).toArray(EntryStack[]::new));
+ }
+
+ @Override
+ public EntryIngredient map(UnaryOperator<EntryStack<?>> transformer) {
+ EntryStack<?>[] out = new EntryStack[array.length];
+ for (int i = 0; i < array.length; i++) {
+ out[i] = transformer.apply(array[i]);
+ }
+ return new ArrayIngredient(out);
+ }
+ }
+}
diff --git a/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/settings/EntrySettingsAdapterRegistryImpl.java b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/settings/EntrySettingsAdapterRegistryImpl.java
new file mode 100644
index 000000000..9380f4b0b
--- /dev/null
+++ b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/settings/EntrySettingsAdapterRegistryImpl.java
@@ -0,0 +1,71 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.entry.settings;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.entry.settings.EntrySettingsAdapter;
+import me.shedaniel.rei.api.common.entry.settings.EntrySettingsAdapterRegistry;
+import me.shedaniel.rei.api.common.entry.type.EntryType;
+import me.shedaniel.rei.api.common.plugins.REIPlugin;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class EntrySettingsAdapterRegistryImpl implements EntrySettingsAdapterRegistry {
+ private final Map<EntryStack.Settings<?>, Multimap<EntryType<?>, EntrySettingsAdapter<?, ?>>> providers = new HashMap<>();
+
+ @Override
+ public <T,S> void register(EntryType<T> type, EntryStack.Settings<S> settings, EntrySettingsAdapter<T,S> provider) {
+ Multimap<EntryType<?>, EntrySettingsAdapter<?, ?>> multimap = this.providers.computeIfAbsent(settings, $ -> Multimaps.newMultimap(new Reference2ObjectOpenHashMap<>(), ArrayList::new));
+ multimap.put(type, provider);
+ }
+
+ @Override
+ @Nullable
+ public <T,S> S adapt(EntryStack<T> stack, EntryStack.Settings<S> settings, @Nullable S value) {
+ Multimap<EntryType<?>, EntrySettingsAdapter<?, ?>> multimap = providers.get(settings);
+ if (multimap != null) {
+ for (EntrySettingsAdapter<T, S> adapter : (Collection<EntrySettingsAdapter<T, S>>) (Collection<? extends EntrySettingsAdapter<?, ?>>) multimap.get(stack.getType())) {
+ value = adapter.provide(stack, settings, value);
+ }
+ }
+ return value;
+ }
+
+ @Override
+ public void startReload() {
+ providers.clear();
+ }
+
+ @Override
+ public void acceptPlugin(REIPlugin<?> plugin) {
+ plugin.registerEntrySettingsAdapters(this);
+ }
+}
diff --git a/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/AbstractEntryStack.java b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/AbstractEntryStack.java
new file mode 100644
index 000000000..9bfe7bfb5
--- /dev/null
+++ b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/AbstractEntryStack.java
@@ -0,0 +1,281 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.entry.stack;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.ClientHelper;
+import me.shedaniel.rei.api.client.gui.Renderer;
+import me.shedaniel.rei.api.client.gui.widgets.Tooltip;
+import me.shedaniel.rei.api.client.gui.widgets.TooltipContext;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.entry.settings.EntrySettingsAdapterRegistry;
+import me.shedaniel.rei.api.common.util.EntryStacks;
+import me.shedaniel.rei.api.common.util.FormattingUtils;
+import me.shedaniel.rei.impl.client.util.CrashReportUtils;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.CrashReport;
+import net.minecraft.CrashReportCategory;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.item.ItemStack;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Iterator;
+import java.util.stream.Stream;
+
+@ApiStatus.Internal
+abstract class AbstractEntryStack<A> implements EntryStack<A>, Renderer {
+ private static final Short2ObjectMap<Object> EMPTY_SETTINGS = Short2ObjectMaps.emptyMap();
+ private Short2ObjectMap<Object> settings = null;
+ @Environment(EnvType.CLIENT)
+ private int blitOffset;
+
+ @Override
+ @Environment(EnvType.CLIENT)
+ public int getZ() {
+ return blitOffset;
+ }
+
+ @Override
+ @Environment(EnvType.CLIENT)
+ public void setZ(int z) {
+ this.blitOffset = z;
+ }
+
+ @Override
+ public <T> EntryStack<A> setting(Settings<T> settings, T value) {
+ short settingsId = settings.getId();
+ if (this.settings == null)
+ this.settings = Short2ObjectMaps.singleton(settingsId, value);
+ else {
+ if (this.settings.size() == 1) {
+ if (this.settings.containsKey(settingsId)) {
+ this.settings = Short2ObjectMaps.singleton(settingsId, value);
+ return this;
+ } else {
+ Short2ObjectMap<Object> singletonSettings = this.settings;
+ this.settings = new Short2ObjectOpenHashMap<>(2);
+ this.settings.putAll(singletonSettings);
+ }
+ }
+ this.settings.put(settingsId, value);
+ }
+ return this;
+ }
+
+ @Override
+ public <T> EntryStack<A> removeSetting(Settings<T> settings) {
+ if (this.settings != null) {
+ short settingsId = settings.getId();
+ if (this.settings.size() == 1) {
+ if (this.settings.containsKey(settingsId)) {
+ this.settings = null;
+ }
+ } else if (this.settings.remove(settingsId) != null && this.settings.isEmpty()) {
+ this.settings = null;
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public EntryStack<A> clearSettings() {
+ this.settings = null;
+ return this;
+ }
+
+ protected Short2ObjectMap<Object> getSettings() {
+ return this.settings == null ? EMPTY_SETTINGS : this.settings;
+ }
+
+ @Override
+ @Nullable
+ public ResourceLocation getIdentifier() {
+ return getDefinition().getIdentifier(this, getValue());
+ }
+
+ @Override
+ @Nullable
+ public String getContainingNamespace() {
+ return get(Settings.CONTAINING_NS).apply(this, getDefinition().getContainingNamespace(this, getValue()));
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return getDefinition().isEmpty(this, getValue());
+ }
+
+ @Override
+ public EntryStack<A> copy() {
+ return wrap(getDefinition().copy(this, getValue()), true);
+ }
+
+ @Override
+ public EntryStack<A> rewrap() {
+ return wrap(getValue(), true);
+ }
+
+ @Override
+ public EntryStack<A> normalize() {
+ return wrap(getDefinition().normalize(this, getValue()), false);
+ }
+
+ @Override
+ public EntryStack<A> wildcard() {
+ return wrap(getDefinition().wildcard(this, getValue()), false);
+ }
+
+ @Override
+ public EntryStack<ItemStack> cheatsAs() {
+ ItemStack stack = getDefinition().cheatsAs(this, getValue());
+
+ if (stack == null) {
+ return EntryStacks.of(ItemStack.EMPTY);
+ }
+
+ return EntryStacks.of(stack);
+ }
+
+ protected EntryStack<A> wrap(A value, boolean copySettings) {
+ TypedEntryStack<A> stack = new TypedEntryStack<>(getDefinition(), value);
+ if (copySettings) {
+ for (Short2ObjectMap.Entry<Object> entry : getSettings().short2ObjectEntrySet()) {
+ stack.setting(EntryStack.Settings.getById(entry.getShortKey()), entry.getValue());
+ }
+ }
+ return stack;
+ }
+
+ @Override
+ public <T> T get(Settings<T> settings) {
+ T o = getNullable(settings);
+ if (o == null) {
+ o = settings.getDefaultValue();
+ }
+ return o;
+ }
+
+ @Override
+ @Nullable
+ public <T> T getNullable(Settings<T> settings) {
+ T o = this.settings == null ? null : (T) this.settings.get(settings.getId());
+ o = EntrySettingsAdapterRegistry.getInstance().adapt(this, settings, o);
+ return o;
+ }
+
+ @Override
+ public void render(PoseStack matrices, Rectangle bounds, int mouseX, int mouseY, float delta) {
+ try {
+ this.getRenderer().render(this, matrices, bounds, mouseX, mouseY, delta);
+ } catch (Throwable throwable) {
+ CrashReport report = CrashReportUtils.essential(throwable, "Rendering entry");
+ CrashReportUtils.renderer(report, this);
+ throw CrashReportUtils.throwReport(report);
+ }
+ }
+
+ @Override
+ @Nullable
+ public Tooltip getTooltip(TooltipContext context, boolean appendModName) {
+ try {
+ Mutable<Tooltip> tooltip = new MutableObject<>(getRenderer().<A>cast().getTooltip(this, context));
+ if (tooltip.getValue() == null) return null;
+ tooltip.getValue().withContextStack(this);
+ tooltip.getValue().addAllTexts(get(Settings.TOOLTIP_APPEND_EXTRA).apply(this));
+ tooltip.setValue(get(Settings.TOOLTIP_PROCESSOR).apply(this, tooltip.getValue()));
+ if (tooltip.getValue() == null) return null;
+ String containingNs = getContainingNamespace();
+ if (appendModName) {
+ if (containingNs != null) {
+ ClientHelper.getInstance().appendModIdToTooltips(tooltip.getValue(), containingNs);
+ }
+ } else if (containingNs != null) {
+ final String modName = ClientHelper.getInstance().getModFromModId(containingNs);
+ Iterator<Tooltip.Entry> iterator = tooltip.getValue().entries().iterator();
+ while (iterator.hasNext()) {
+ Tooltip.Entry s = iterator.next();
+ if (s.isText() && FormattingUtils.stripFormatting(s.getAsText().getString()).equalsIgnoreCase(modName)) {
+ iterator.remove();
+ }
+ }
+ }
+ return tooltip.getValue();
+ } catch (Throwable throwable) {
+ CrashReport report = CrashReportUtils.essential(throwable, "Getting tooltips");
+ CrashReportUtils.renderer(report, this);
+ throw CrashReportUtils.throwReport(report);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AbstractEntryStack<?> that)) return false;
+ return EntryStacks.equalsExact(this, that);
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(EntryStacks.hashExact(this));
+ }
+
+ @Override
+ public Stream<TagKey<?>> getTagsFor() {
+ return (Stream<TagKey<?>>) getDefinition().getTagsFor(this, getValue());
+ }
+
+ @Override
+ public Component asFormattedText() {
+ return getDefinition().asFormattedText(this, getValue(), TooltipContext.of());
+ }
+
+ @Override
+ public Component asFormattedText(TooltipContext context) {
+ return getDefinition().asFormattedText(this, getValue(), context);
+ }
+
+ @Override
+ public void fillCrashReport(CrashReport report, CrashReportCategory category) {
+ EntryStack.super.fillCrashReport(report, category);
+ category.setDetail("Entry type", () -> String.valueOf(getType().getId()));
+ category.setDetail("Is empty", () -> String.valueOf(isEmpty()));
+ category.setDetail("Entry identifier", () -> String.valueOf(getIdentifier()));
+
+ CrashReportCategory rendererCategory = report.addCategory("Entry Renderer");
+ try {
+ getDefinition().fillCrashReport(report, rendererCategory, this);
+ } catch (Throwable throwable) {
+ rendererCategory.setDetailError("Filling Report", throwable);
+ }
+ }
+}
diff --git a/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/EmptyEntryStack.java b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/EmptyEntryStack.java
new file mode 100644
index 000000000..43030eb92
--- /dev/null
+++ b/runtime-engine/entry-stacks/src/main/java/me/shedaniel/rei/impl/common/entry/stack/EmptyEntryStack.java
@@ -0,0 +1,45 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.entry.stack;
+
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.entry.type.BuiltinEntryTypes;
+import me.shedaniel.rei.api.common.entry.type.EntryDefinition;
+import net.minecraft.util.Unit;
+import org.jetbrains.annotations.ApiStatus;
+