Skip to content

Commit 3093483

Browse files
move most logic from mixin to capability
1 parent d8f673e commit 3093483

File tree

5 files changed

+160
-142
lines changed

5 files changed

+160
-142
lines changed

src/main/java/com/gregtechceu/gtceu/api/item/ISpoilableItemStackExtension.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import com.gregtechceu.gtceu.api.item.component.SpoilContext;
44

5-
import org.jetbrains.annotations.NotNull;
5+
import net.minecraft.world.item.ItemStack;
66

77
public interface ISpoilableItemStackExtension {
88

9-
void gtceu$updateFreshness(@NotNull SpoilContext spoilContext, boolean createTag);
9+
void gtceu$setStack(ItemStack newStack);
10+
11+
void gtceu$setSpoilContext(SpoilContext ctx);
1012

1113
SpoilContext gtceu$getSpoilContext();
1214
}

src/main/java/com/gregtechceu/gtceu/api/item/component/ISpoilableItem.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import net.minecraftforge.event.AttachCapabilitiesEvent;
1515
import net.minecraftforge.items.IItemHandler;
1616

17+
import java.util.Optional;
18+
1719
/**
1820
* This is a capability! {@link Item} subclasses should not implement this directly!
1921
* <br>
@@ -150,4 +152,17 @@ public interface ISpoilableItem {
150152
* @param tick the value to set to
151153
*/
152154
void setCreationTick(long tick);
155+
156+
/**
157+
* Called when {@link ItemStack#isSameItemSameTags(ItemStack, ItemStack)} is called.
158+
* If this returns an empty optional, {@link ItemStack#isSameItemSameTags} will return its
159+
* normal value, otherwise it will return the same value as this method.
160+
* <br>
161+
* This exists mostly for custom spoilable merging logic.
162+
*
163+
* @return whether these two stacks should be considered equal
164+
*/
165+
default Optional<Boolean> isEqualTo(ItemStack other) {
166+
return Optional.empty();
167+
};
153168
}

src/main/java/com/gregtechceu/gtceu/api/item/component/SpoilUtils.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,6 @@
1616

1717
public class SpoilUtils {
1818

19-
/**
20-
* Consider frozen and non-frozen spoilables equal. This is done to allow filtering by ticks remaining until
21-
* spoiled.<br>
22-
* If you want the player to have frozen stacks in their inventory, set this to {@code false} to prevent players
23-
* from
24-
* entirely bypassing the spoilage system.
25-
*/
26-
public static boolean FROZEN_EQUALITY = true;
27-
2819
/**
2920
* Initializes this ItemStack's spoilage timer if it wasn't initialized before.
3021
* Should be called when it finishes crafting, for example.

src/main/java/com/gregtechceu/gtceu/common/item/SpoilableItemStack.java

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper;
44
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.*;
96
import com.gregtechceu.gtceu.utils.FormattingUtil;
107

118
import net.minecraft.ChatFormatting;
@@ -22,6 +19,8 @@
2219
import org.jetbrains.annotations.Nullable;
2320

2421
import java.util.List;
22+
import java.util.Objects;
23+
import java.util.Optional;
2524

2625
/**
2726
* This class is a basic implementation of the {@link ISpoilableItem} capability,
@@ -36,15 +35,80 @@
3635
*/
3736
public abstract class SpoilableItemStack implements ISpoilableItem, IAddInformation, IDurabilityBar {
3837

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;
3946
@Getter
4047
private final ItemStack stack;
4148

4249
public SpoilableItemStack(ItemStack stack) {
4350
this.stack = stack;
4451
}
4552

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+
*/
4676
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+
}
48112
}
49113

50114
@Override
@@ -136,7 +200,7 @@ public void appendHoverText(ItemStack stack, @Nullable Level level, List<Compone
136200
tooltipComponents.add(Component.translatable(
137201
"gtceu.tooltip.creation_tick",
138202
spoilable.getCreationTick()));
139-
SpoilContext ctx = ((ISpoilableItemStackExtension) (Object) stack).gtceu$getSpoilContext();
203+
SpoilContext ctx = cast().gtceu$getSpoilContext();
140204
if (ctx.level() != null && ctx.pos() != null)
141205
tooltipComponents.add(Component.translatable("gtceu.tooltip.location",
142206
ctx.level().dimensionTypeId().location().toString(),
@@ -173,4 +237,48 @@ public float getDurabilityForDisplay(ItemStack stack) {
173237
if (spoilable == null) return 0;
174238
return (float) spoilable.getTicksUntilSpoiled() / spoilable.getSpoilTicks();
175239
}
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+
}
176284
}

0 commit comments

Comments
 (0)