Skip to content

Commit 8890bf0

Browse files
committed
internal: rework project bench crafting logic and add tests
1 parent 16e27e0 commit 8890bf0

File tree

13 files changed

+1935
-94
lines changed

13 files changed

+1935
-94
lines changed

core/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@ neoForge {
1818
}
1919
}
2020

21+
mods {
22+
"${mod_id}" {
23+
sourceSet(sourceSets.main)
24+
}
25+
}
26+
2127
unitTest {
2228
enable()
29+
testedMod = mods.getByName(mod_id)
2330
}
2431
}
2532

@@ -44,6 +51,8 @@ dependencies {
4451
compileOnly("cc.tweaked:cc-tweaked-${mc_version}-forge-api:${cct_version}")
4552
runtimeOnly("cc.tweaked:cc-tweaked-${mc_version}-forge:${cct_version}")
4653

54+
testRuntimeOnly project(":api") // api is compileOnly (JarJar'd into jar), but needed at test runtime since we run from source classes
55+
4756
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
4857
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
4958
}
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
package mrtjp.projectred.core.inventory;
2+
3+
import net.minecraft.world.Container;
4+
import net.minecraft.world.entity.player.Player;
5+
import net.minecraft.world.entity.player.StackedContents;
6+
import net.minecraft.world.inventory.StackedContentsCompatible;
7+
import net.minecraft.world.item.Item;
8+
import net.minecraft.world.item.ItemStack;
9+
10+
import javax.annotation.Nullable;
11+
import java.util.ArrayList;
12+
import java.util.HashSet;
13+
import java.util.Set;
14+
15+
/**
16+
* Container that overlays a real container to allow item modification without effecting the underlying container.
17+
* The data can then be written back if desired.
18+
* <p>
19+
* ItemStacks are only copied on access. If underlying is changed before first access, the new change will
20+
* reflect in this layer.
21+
*/
22+
public class OverlayContainer implements Container, StackedContentsCompatible {
23+
24+
private final ArrayList<OverlayItem> items;
25+
private final int maxStackSize;
26+
27+
private OverlayContainer(ArrayList<OverlayItem> items, int maxStackSize) {
28+
this.items = items;
29+
this.maxStackSize = maxStackSize;
30+
}
31+
32+
//region Container
33+
34+
@Override
35+
public int getContainerSize() {
36+
return items.size();
37+
}
38+
39+
@Override
40+
public boolean isEmpty() {
41+
for (OverlayItem overlay : items) {
42+
if (!overlay.getItemNoCopy().isEmpty()) {
43+
return false;
44+
}
45+
}
46+
return true;
47+
}
48+
49+
@Override
50+
public ItemStack getItem(int i) {
51+
return items.get(i).getItem();
52+
}
53+
54+
@Override
55+
public ItemStack removeItem(int i, int amount) {
56+
if (amount < 0) {
57+
return ItemStack.EMPTY;
58+
}
59+
var overlay = items.get(i);
60+
if (overlay.getItemNoCopy().isEmpty()) {
61+
return ItemStack.EMPTY;
62+
}
63+
var result = overlay.getItem().split(amount);
64+
setChanged();
65+
return result;
66+
}
67+
68+
@Override
69+
public ItemStack removeItemNoUpdate(int i) {
70+
var overlay = items.get(i);
71+
if (overlay.getItemNoCopy().isEmpty()) {
72+
return ItemStack.EMPTY;
73+
}
74+
var result = overlay.getItem();
75+
overlay.setItem(ItemStack.EMPTY);
76+
return result;
77+
}
78+
79+
@Override
80+
public void setItem(int i, ItemStack stack) {
81+
var overlay = items.get(i);
82+
overlay.setItem(stack);
83+
stack.limitSize(getMaxStackSize(stack));
84+
}
85+
86+
@Override
87+
public void setChanged() {
88+
}
89+
90+
@Override
91+
public boolean stillValid(Player player) {
92+
return false;
93+
}
94+
95+
@Override
96+
public void clearContent() {
97+
for (OverlayItem overlay : items) {
98+
overlay.setItem(ItemStack.EMPTY);
99+
}
100+
setChanged();
101+
}
102+
103+
@Override
104+
public int getMaxStackSize() {
105+
return maxStackSize;
106+
}
107+
108+
// Re-implement default to prevent unnecessary copy
109+
@Override
110+
public int countItem(Item item) {
111+
int result = 0;
112+
for (OverlayItem overlay : items) {
113+
var stack = overlay.getItemNoCopy();
114+
if (stack.getItem().equals(item)) {
115+
result += stack.getCount();
116+
}
117+
}
118+
return result;
119+
}
120+
//endregion
121+
122+
//region StackedContentsCompatible
123+
@Override
124+
public void fillStackedContents(StackedContents contents) {
125+
for (OverlayItem overlay : items) {
126+
contents.accountStack(overlay.getItemNoCopy());
127+
}
128+
}
129+
//endregion
130+
131+
//region Overlay utils
132+
133+
/**
134+
* Drop any changes and go back to underlying container's state
135+
*/
136+
public void clearChanges() {
137+
for (OverlayItem overlay : items) {
138+
overlay.clear();
139+
}
140+
}
141+
142+
/**
143+
* Write any changes back to the underlying containers.
144+
*/
145+
public void commitChanges() {
146+
for (OverlayItem overlay : items) {
147+
overlay.commit();
148+
}
149+
}
150+
151+
/**
152+
* Force-copies all items into this layer even if unchanged.
153+
*/
154+
public void copyUp() {
155+
for (OverlayItem overlay : items) {
156+
overlay.getItem();
157+
}
158+
}
159+
160+
/**
161+
* Create another overlay layer.
162+
*
163+
* @return An overlay on top of this overlay
164+
*/
165+
public OverlayContainer newLayer() {
166+
return new Builder().addItems(this).build();
167+
}
168+
169+
public static OverlayContainer.Builder builder() {
170+
return new Builder();
171+
}
172+
173+
public static OverlayContainer of(Container container) {
174+
return new Builder().addItems(container).build();
175+
}
176+
//endregion
177+
178+
public static class Builder {
179+
180+
private ArrayList<OverlayItem> items = new ArrayList<>();
181+
private Set<Integer> maxStackSizes = new HashSet<>();
182+
183+
private Builder() {
184+
}
185+
186+
public Builder addItems(Container container) {
187+
return addItems(container, 0, container.getContainerSize());
188+
}
189+
190+
public Builder addItems(Container src, int srcPos, int length) {
191+
for (int i = srcPos; i < srcPos + length; i++) {
192+
addItem(src, i);
193+
}
194+
return this;
195+
}
196+
197+
public Builder addItem(Container src, int srcPos) {
198+
items.add(new OverlayItem(src, srcPos));
199+
maxStackSizes.add(src.getMaxStackSize());
200+
return this;
201+
}
202+
203+
public OverlayContainer build() {
204+
if (maxStackSizes.size() > 1) {
205+
throw new RuntimeException("Cannot overlay containers with different max stack sizes: " + maxStackSizes);
206+
}
207+
int maxStackSize = maxStackSizes.isEmpty() ? 64 : maxStackSizes.iterator().next();
208+
return new OverlayContainer(items, maxStackSize);
209+
}
210+
}
211+
212+
private static class OverlayItem {
213+
214+
private final Container src;
215+
private final int srcPos;
216+
217+
@Nullable
218+
private ItemStack item = null;
219+
220+
public OverlayItem(Container src, int srcPos) {
221+
this.src = src;
222+
this.srcPos = srcPos;
223+
}
224+
225+
public ItemStack getItem() {
226+
// Copy-on-read
227+
if (item == null) {
228+
item = src.getItem(srcPos).copy();
229+
}
230+
return item;
231+
}
232+
233+
// INTERNAL USE ONLY
234+
public ItemStack getItemNoCopy() {
235+
if (item == null) {
236+
return src.getItem(srcPos);
237+
}
238+
return item;
239+
}
240+
241+
public void setItem(ItemStack item) {
242+
this.item = item;
243+
}
244+
245+
public void clear() {
246+
item = null;
247+
}
248+
249+
public void commit() {
250+
if (item != null) {
251+
src.setItem(srcPos, item);
252+
}
253+
}
254+
255+
@Override
256+
public String toString() {
257+
// String includes src, srcPos, upper and lower item
258+
return String.format("OverlayItem(src=%s, srcPos=%d, lowerItem=%s, upperItem=%s)", src, srcPos, getItemNoCopy(), item);
259+
}
260+
}
261+
}

core/src/main/java/mrtjp/projectred/lib/InventoryLib.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import net.neoforged.neoforge.items.IItemHandler;
1111

1212
import java.util.function.Consumer;
13+
import java.util.function.Predicate;
1314

1415
public class InventoryLib {
1516

@@ -70,6 +71,30 @@ private static void injectItemStack(Container inventory, ItemStack stack, int st
7071
}
7172
}
7273

74+
public static int removeItems(Container inventory, Predicate<ItemStack> matchFunc, int amount, boolean reverse) {
75+
return removeItems(inventory, matchFunc, amount, 0, inventory.getContainerSize(), reverse);
76+
}
77+
78+
public static int removeItems(Container inventory, Predicate<ItemStack> matchFunc, int amount, int startIndex, int endIndex, boolean reverse) {
79+
int removed = 0;
80+
for (int i = startIndex; i < endIndex; i++) {
81+
int index = reverse ? endIndex - i - 1 : i;
82+
83+
ItemStack stackInSlot = inventory.getItem(index);
84+
if (stackInSlot.isEmpty() || !matchFunc.test(stackInSlot)) continue;
85+
86+
int amountToExtract = Math.min(amount, stackInSlot.getCount());
87+
ItemStack taken = inventory.removeItem(index, amountToExtract);
88+
if (!taken.isEmpty()) {
89+
amount -= taken.getCount();
90+
removed += taken.getCount();
91+
}
92+
if (amount <= 0) break;
93+
}
94+
95+
return removed;
96+
}
97+
7398
//region Worldly Container utilities
7499
public static boolean injectWorldly(WorldlyContainer container, ItemStack stack, int side, boolean simulate) {
75100

0 commit comments

Comments
 (0)