Skip to content

Commit 0b895ad

Browse files
authored
Record gem and material inputs on alloy scrap (#30)
* feat: record all failed-forge inputs on scrap * test: cover scrap without item metadata * fix: skip scrap quantities stored with the wrong type --------- Co-authored-by: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com>
1 parent 2e46fb6 commit 0b895ad

6 files changed

Lines changed: 89 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,5 @@ runs when a report was generated.
5959

6060
Coverage proves the exercised Java behavior. It does not replace a live Paper
6161
server integration check with the pinned ItemsAdder/MMOItems/TLibs versions.
62+
63+
Failed alloy forges record consumed ingredient quantities on scrap for Recycler recovery. Older scrap retains only its recorded base; missing catalyst history cannot be recovered.

‎src/main/java/net/tfminecraft/advancedcrafting/objects/alloys/AlloyForger.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ public NamableAlloy forge(Player p, int i) {
9696
ItemManager itemManager = MMOItems.plugin.getItems();
9797
ItemStack template = itemManager.getMMOItem(MMOItems.plugin.getTypes().get(scrapType),scrapId).newBuilder().build();
9898
if(station.getBaseItem() != null) ScrapProvenance.applyTo(template, station.getBaseItem().getId());
99+
var consumed = new java.util.LinkedHashMap<String, Integer>();
100+
for (Ingredient ingredient : station.getIngredients()) {
101+
consumed.merge(ingredient.getId(), 1, Integer::sum);
102+
}
103+
ScrapProvenance.applyInputs(template, consumed);
99104
loc.getWorld().dropItem(loc, template);
100105
return null;
101106
}

‎src/main/java/net/tfminecraft/advancedcrafting/objects/data/ScrapProvenance.java‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package net.tfminecraft.advancedcrafting.objects.data;
22

33
import java.util.Locale;
4+
import java.util.LinkedHashMap;
5+
import java.util.Map;
6+
7+
import org.bukkit.NamespacedKey;
48

59
import org.bukkit.inventory.ItemStack;
610
import org.bukkit.inventory.meta.ItemMeta;
711
import org.bukkit.persistence.PersistentDataType;
812

913
import net.tfminecraft.advancedcrafting.utils.PDCKeys;
14+
import net.tfminecraft.advancedcrafting.AdvancedCrafting;
1015

1116
/**
1217
* Records which base ingredient a failed alloy forge consumed, so scrap can be recycled into it.
@@ -34,4 +39,34 @@ public static String readBaseId(ItemStack item) {
3439
}
3540
return item.getItemMeta().getPersistentDataContainer().get(PDCKeys.scrapBase(), PersistentDataType.STRING);
3641
}
42+
43+
/** Records consumed ingredient quantities per scrap, independently of the legacy base tag. */
44+
public static void applyInputs(ItemStack item, Map<String, Integer> amounts) {
45+
ItemMeta meta = item.getItemMeta();
46+
var container = meta.getPersistentDataContainer();
47+
var inputs = container.getAdapterContext().newPersistentDataContainer();
48+
amounts.forEach((id, amount) -> inputs.set(
49+
new NamespacedKey(AdvancedCrafting.plugin, id.toLowerCase(Locale.ROOT)),
50+
PersistentDataType.INTEGER, amount));
51+
container.set(PDCKeys.scrapInputs(), PersistentDataType.TAG_CONTAINER, inputs);
52+
item.setItemMeta(meta);
53+
}
54+
55+
/** Legacy scrap contains only a base tag; its catalysts cannot be reconstructed. */
56+
public static Map<String, Integer> readInputs(ItemStack item) {
57+
if (item == null || !item.hasItemMeta()) return Map.of();
58+
var inputs = item.getItemMeta().getPersistentDataContainer().get(
59+
PDCKeys.scrapInputs(), PersistentDataType.TAG_CONTAINER);
60+
if (inputs == null) {
61+
String base = readBaseId(item);
62+
return base == null || base.isBlank() ? Map.of() : Map.of(base, 1);
63+
}
64+
Map<String, Integer> amounts = new LinkedHashMap<>();
65+
for (var key : inputs.getKeys()) {
66+
if (!inputs.has(key, PersistentDataType.INTEGER)) continue;
67+
int amount = inputs.get(key, PersistentDataType.INTEGER);
68+
if (amount > 0) amounts.put(key.getKey(), amount);
69+
}
70+
return amounts;
71+
}
3772
}

‎src/main/java/net/tfminecraft/advancedcrafting/utils/PDCKeys.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ public static NamespacedKey scrapBase() {
2828
return new NamespacedKey(AdvancedCrafting.plugin, "ac_scrap_base");
2929
}
3030

31+
public static NamespacedKey scrapInputs() {
32+
return new NamespacedKey(AdvancedCrafting.plugin, "ac_scrap_inputs");
33+
}
34+
3135
public static NamespacedKey ingredientId() {
3236
return new NamespacedKey(AdvancedCrafting.plugin, "ac_ingredient_id");
3337
}

‎src/test/java/net/tfminecraft/advancedcrafting/ForgerCoverageTest.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ void forgeReturnsScrapKnownAlloysNewDiscoveriesAndRecoversStaleIndexes() throws
7878
try (var dbs = mockConstruction(AlloyDatabase.class)) {
7979
assertNull(forger(station, 0).forge(p));
8080
verify(dbs.constructed().getFirst()).saveRecipe(station, "scrap");
81+
var dropped = loc.getWorld().getEntitiesByClass(org.bukkit.entity.Item.class).iterator().next().getItemStack();
82+
assertEquals(Map.of("iron", 1, "copper", 1), ScrapProvenance.readInputs(dropped));
8183
}
8284
try (var dbs =
8385
mockConstruction(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package net.tfminecraft.advancedcrafting;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
import java.util.Map;
5+
import net.tfminecraft.advancedcrafting.objects.data.ScrapProvenance;
6+
import net.tfminecraft.advancedcrafting.utils.PDCKeys;
7+
import org.bukkit.Material;
8+
import org.bukkit.NamespacedKey;
9+
import org.bukkit.inventory.ItemStack;
10+
import org.bukkit.persistence.PersistentDataType;
11+
import org.junit.jupiter.api.Test;
12+
13+
class ScrapInputsTest extends CoverageSupport {
14+
@Test
15+
void inputsRoundTripQuantitiesAndLegacyScrapReturnsOnlyItsBase() {
16+
var scrap = new ItemStack(Material.IRON_NUGGET);
17+
assertTrue(ScrapProvenance.readInputs(null).isEmpty());
18+
assertTrue(ScrapProvenance.readInputs(org.mockito.Mockito.mock(ItemStack.class)).isEmpty());
19+
assertTrue(ScrapProvenance.readInputs(scrap).isEmpty());
20+
scrap.setItemMeta(scrap.getItemMeta());
21+
var meta = scrap.getItemMeta();
22+
meta.setDisplayName("Scrap");
23+
scrap.setItemMeta(meta);
24+
assertTrue(ScrapProvenance.readInputs(scrap).isEmpty());
25+
ScrapProvenance.applyTo(scrap, "IRON");
26+
assertEquals(Map.of("iron", 1), ScrapProvenance.readInputs(scrap));
27+
ScrapProvenance.applyInputs(scrap, Map.of("IRON", 2, "ruby", 3, "zero", 0));
28+
assertEquals(Map.of("iron", 2, "ruby", 3), ScrapProvenance.readInputs(scrap));
29+
meta = scrap.getItemMeta();
30+
var inputs = meta.getPersistentDataContainer().get(PDCKeys.scrapInputs(), PersistentDataType.TAG_CONTAINER);
31+
inputs.set(new NamespacedKey(plugin, "bad"), PersistentDataType.STRING, "bad");
32+
meta.getPersistentDataContainer().set(PDCKeys.scrapInputs(), PersistentDataType.TAG_CONTAINER, inputs);
33+
scrap.setItemMeta(meta);
34+
assertEquals(Map.of("iron", 2, "ruby", 3), ScrapProvenance.readInputs(scrap));
35+
meta = scrap.getItemMeta();
36+
meta.getPersistentDataContainer().remove(PDCKeys.scrapInputs());
37+
meta.getPersistentDataContainer().set(PDCKeys.scrapBase(), PersistentDataType.STRING, " ");
38+
scrap.setItemMeta(meta);
39+
assertTrue(ScrapProvenance.readInputs(scrap).isEmpty());
40+
}
41+
}

0 commit comments

Comments
 (0)