|
2 | 2 |
|
3 | 3 | import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; |
4 | 4 | import com.gregtechceu.gtceu.api.item.ISpoilableItemStackExtension; |
5 | | -import com.gregtechceu.gtceu.api.item.component.IAddInformation; |
6 | | -import com.gregtechceu.gtceu.api.item.component.IDurabilityBar; |
7 | | -import com.gregtechceu.gtceu.api.item.component.ISpoilableItem; |
8 | | -import com.gregtechceu.gtceu.api.item.component.SpoilContext; |
| 5 | +import com.gregtechceu.gtceu.api.item.component.*; |
9 | 6 | import com.gregtechceu.gtceu.utils.FormattingUtil; |
10 | 7 |
|
11 | 8 | import net.minecraft.ChatFormatting; |
|
22 | 19 | import org.jetbrains.annotations.Nullable; |
23 | 20 |
|
24 | 21 | import java.util.List; |
| 22 | +import java.util.Objects; |
| 23 | +import java.util.Optional; |
25 | 24 |
|
26 | 25 | /** |
27 | 26 | * This class is a basic implementation of the {@link ISpoilableItem} capability, |
|
36 | 35 | */ |
37 | 36 | public abstract class SpoilableItemStack implements ISpoilableItem, IAddInformation, IDurabilityBar { |
38 | 37 |
|
| 38 | + /** |
| 39 | + * Consider frozen and non-frozen spoilables equal. This is done to allow filtering by ticks remaining until |
| 40 | + * spoiled.<br> |
| 41 | + * If you want the player to have frozen stacks in their inventory, set this to {@code false} to prevent players |
| 42 | + * from |
| 43 | + * entirely bypassing the spoilage system. |
| 44 | + */ |
| 45 | + public static boolean FROZEN_EQUALITY = true; |
39 | 46 | @Getter |
40 | 47 | private final ItemStack stack; |
41 | 48 |
|
42 | 49 | public SpoilableItemStack(ItemStack stack) { |
43 | 50 | this.stack = stack; |
44 | 51 | } |
45 | 52 |
|
| 53 | + private ISpoilableItemStackExtension cast() { |
| 54 | + return (ISpoilableItemStackExtension) (Object) stack; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Checks if this item should've already spoiled, and calls |
| 59 | + * {@link ISpoilableItem#spoilResult(SpoilContext, boolean)} |
| 60 | + * with {@link com.gregtechceu.gtceu.core.mixins.ItemStackMixin#gtceu$getSpoilContext()} |
| 61 | + * and replaces this item with its return value if so.<br> |
| 62 | + * Also sets the {@link SpoilContext} stored in the mixin to the provided |
| 63 | + * context if it is non-empty (determined by {@link SpoilContext#isEmpty()}).<br> |
| 64 | + * <br> |
| 65 | + * If {@code createTag = true} and the spoilage tag did not exist, creates |
| 66 | + * the tag and sets the creation tick to this tick.<br> |
| 67 | + * If {@code createTag = false} and the spoilage tag isn't present, does nothing. |
| 68 | + * |
| 69 | + * @param createTag Whether to create a spoilage tag if it wasn't present. |
| 70 | + * Usually {@code true} for stacks that are present in-world, |
| 71 | + * and {@code false} for stacks in XEI, icons, quests, etc. |
| 72 | + * |
| 73 | + * @implNote This method is injected into all of {@link ItemStack}'s getters to |
| 74 | + * be called with an empty {@link SpoilContext} and {@code createTag = false}. |
| 75 | + */ |
46 | 76 | public void updateFreshness(SpoilContext spoilContext, boolean createTag) { |
47 | | - ((ISpoilableItemStackExtension) (Object) stack).gtceu$updateFreshness(spoilContext, createTag); |
| 77 | + if (!spoilContext.isEmpty()) cast().gtceu$setSpoilContext(spoilContext); |
| 78 | + Level level = SpoilContext.getDefaultLevel(); |
| 79 | + ISpoilableItem spoilable = GTCapabilityHelper.getSpoilable(stack); |
| 80 | + if (spoilable != null && spoilable.shouldSpoil()) { |
| 81 | + if (spoilable.getSpoilTicks() < 0) { |
| 82 | + return; |
| 83 | + } |
| 84 | + CompoundTag tag = createTag ? stack.getOrCreateTagElement("GTCEu_spoilable") : |
| 85 | + stack.getTagElement("GTCEu_spoilable"); |
| 86 | + if (tag == null || tag.contains("frozenRemainingTicks")) { |
| 87 | + return; |
| 88 | + } |
| 89 | + if (level == null) { |
| 90 | + return; |
| 91 | + } |
| 92 | + if (!tag.contains("creation_tick")) { |
| 93 | + tag.putLong("creation_tick", level.getGameTime()); |
| 94 | + } |
| 95 | + long spoilTicks = spoilable.getSpoilTicks(); |
| 96 | + long timeDifference = level.getGameTime() - tag.getLong("creation_tick") - spoilTicks; |
| 97 | + if (timeDifference >= 0) { |
| 98 | + ItemStack newStack = spoilable.spoilResult(cast().gtceu$getSpoilContext(), false); |
| 99 | + cast().gtceu$setStack(newStack); |
| 100 | + ISpoilableItem newSpoilable = GTCapabilityHelper.getSpoilable(stack); |
| 101 | + if (newSpoilable != null && (stack.getTag() == null || !stack.getTag().contains("GTCEu_spoilable"))) { |
| 102 | + stack.getOrCreateTagElement("GTCEu_spoilable").putLong("creation_tick", |
| 103 | + level.getGameTime() - timeDifference); |
| 104 | + try { |
| 105 | + updateFreshness(spoilContext, false); |
| 106 | + } catch (StackOverflowError ignored) { |
| 107 | + // if items spoil in a giant chain or a loop |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | + } |
48 | 112 | } |
49 | 113 |
|
50 | 114 | @Override |
@@ -136,7 +200,7 @@ public void appendHoverText(ItemStack stack, @Nullable Level level, List<Compone |
136 | 200 | tooltipComponents.add(Component.translatable( |
137 | 201 | "gtceu.tooltip.creation_tick", |
138 | 202 | spoilable.getCreationTick())); |
139 | | - SpoilContext ctx = ((ISpoilableItemStackExtension) (Object) stack).gtceu$getSpoilContext(); |
| 203 | + SpoilContext ctx = cast().gtceu$getSpoilContext(); |
140 | 204 | if (ctx.level() != null && ctx.pos() != null) |
141 | 205 | tooltipComponents.add(Component.translatable("gtceu.tooltip.location", |
142 | 206 | ctx.level().dimensionTypeId().location().toString(), |
@@ -173,4 +237,48 @@ public float getDurabilityForDisplay(ItemStack stack) { |
173 | 237 | if (spoilable == null) return 0; |
174 | 238 | return (float) spoilable.getTicksUntilSpoiled() / spoilable.getSpoilTicks(); |
175 | 239 | } |
| 240 | + |
| 241 | + /** |
| 242 | + * Since {@link ItemStack#isSameItemSameTags(ItemStack, ItemStack)} is commonly called |
| 243 | + * right before merging two stacks, this method averages their spoil progress (or, more |
| 244 | + * accurately, their {@link ISpoilableItem#getCreationTick()}). If {@link SpoilableItemStack#FROZEN_EQUALITY} |
| 245 | + * is {@code true}, this method will ignore the frozen/not frozen status of stacks when determining its |
| 246 | + * return value. Other than that, the return value is equal to the normal {@link ItemStack#isSameItemSameTags}. |
| 247 | + * |
| 248 | + * @implNote This implementation may lead to spoil progress averaging in situations other |
| 249 | + * than stack merging, though I don't think this will lead to any big user-facing bugs. |
| 250 | + */ |
| 251 | + @Override |
| 252 | + public Optional<Boolean> isEqualTo(ItemStack other) { |
| 253 | + ISpoilableItem spoilable1 = this; |
| 254 | + ISpoilableItem spoilable2 = GTCapabilityHelper.getSpoilable(other); |
| 255 | + if (spoilable2 != null && !(spoilable2 instanceof SpoilableItemStack)) return spoilable2.isEqualTo(stack); |
| 256 | + boolean isSameItem = ItemStack.isSameItem(stack, other) && stack.areCapsCompatible(other); |
| 257 | + CompoundTag modifiedTag1 = stack.getTag() == null ? null : stack.getTag().copy(); |
| 258 | + CompoundTag modifiedTag2 = other.getTag() == null ? null : other.getTag().copy(); |
| 259 | + if (modifiedTag1 != null) modifiedTag1.remove("GTCEu_spoilable"); |
| 260 | + if (modifiedTag2 != null) modifiedTag2.remove("GTCEu_spoilable"); |
| 261 | + isSameItem = isSameItem && Objects.equals(modifiedTag1, modifiedTag2); |
| 262 | + if (isSameItem && spoilable2 != null) { |
| 263 | + if (spoilable1.isFrozen() || spoilable2.isFrozen()) { |
| 264 | + if (!FROZEN_EQUALITY && (spoilable1.isFrozen() ^ spoilable2.isFrozen())) { |
| 265 | + return Optional.of(false); |
| 266 | + } |
| 267 | + return Optional.of(spoilable1.getTicksUntilSpoiled() == spoilable2.getTicksUntilSpoiled()); |
| 268 | + } else { |
| 269 | + long tick1 = spoilable1.getCreationTick(); |
| 270 | + long tick2 = spoilable2.getCreationTick(); |
| 271 | + if (tick1 != tick2) { |
| 272 | + long avg; |
| 273 | + if (stack.getCount() + other.getCount() > 0) |
| 274 | + avg = (tick1 * stack.getCount() + tick2 * other.getCount()) / |
| 275 | + (stack.getCount() + other.getCount()); |
| 276 | + else avg = tick1; |
| 277 | + spoilable1.setCreationTick(avg); |
| 278 | + spoilable2.setCreationTick(avg); |
| 279 | + } |
| 280 | + } |
| 281 | + } |
| 282 | + return Optional.empty(); |
| 283 | + } |
176 | 284 | } |
0 commit comments