Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ GemInfusion gives gemstones rolled bonuses and turns them into a resource for eq
- **Rarity and stat rolls** — gems range from common to legendary and provide bonuses such as health, armour, damage, or damage reduction according to their type.
- **Character influence** — Intelligence affects an infused gem's rolled stat, while Dexterity separately influences the bonus carried into jewellery.
- **Socketed equipment** — connects gems with MMOItems sockets and preserves rarity information through socketing and removal.
- **Goldsmithing projects** — craft rings, necklaces, medals, and other pieces from metal materials and an infused gem at a smithing station.
- **Goldsmithing projects** — craft rings, necklaces, medals, and other pieces from metal materials and an infused gem at a smithing station. Each finished piece records the metal materials actually used, so Recycler returns those rather than the listed recipe.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
- **Craftsmanship matters** — recipe accuracy, tool work, project tier, and finishing quality shape how much of the gem's bonus reaches the finished piece.

The same gemstone links discovery and craft: its infusion determines the starting bonus, and the goldsmith's work shapes the jewellery made from it. Legendary infusions can also be announced to other players.
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/net/tfminecraft/geminfusion/PDCKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ public static NamespacedKey gemRarity() {
public static NamespacedKey socketRarities() {
return new NamespacedKey(InfusionMain.plugin, "socket_rarities");
}

public static NamespacedKey goldsmithInputs() {
return new NamespacedKey(InfusionMain.plugin, "goldsmith_inputs");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package net.tfminecraft.geminfusion.goldsmith;

import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;

import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;

import net.tfminecraft.geminfusion.PDCKeys;

/**
* Records the materials a finished jewelry piece was actually made from, as item path to amount.
* A slot accepts any material of its type, so this can differ from the project recipe.
*/
public final class GoldsmithProvenance {

private static final Gson GSON = new Gson();
private static final Type INPUTS_TYPE = new TypeToken<LinkedHashMap<String, Integer>>() {
}.getType();

private GoldsmithProvenance() {
}

public static void stamp(ItemStack item, Map<GoldsmithMaterial, Integer> deposited) {
LinkedHashMap<String, Integer> inputs = new LinkedHashMap<>();
for (Map.Entry<GoldsmithMaterial, Integer> entry : deposited.entrySet()) {
inputs.merge(entry.getKey().getPath(), entry.getValue(), Integer::sum);
}
ItemMeta meta = item.getItemMeta();
meta.getPersistentDataContainer().set(PDCKeys.goldsmithInputs(), PersistentDataType.STRING,
GSON.toJson(inputs));
item.setItemMeta(meta);
}

/**
* @return deposited material paths and amounts, or null for jewelry made before inputs were recorded
*/
public static Map<String, Integer> read(ItemStack item) {
if (item == null || !item.hasItemMeta()) {
return null;
}
String json = item.getItemMeta().getPersistentDataContainer().get(PDCKeys.goldsmithInputs(),
PersistentDataType.STRING);
if (json == null) {
return null;
}
try {
return GSON.fromJson(json, INPUTS_TYPE);
} catch (JsonParseException ex) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public static JewelryCraftResult build(GoldsmithStation station, Player player)
return null;
}
out.setAmount(1);
GoldsmithProvenance.stamp(out, station.getDepositedByMaterial());
return new JewelryCraftResult(out, recipePct, hitPct, finishedTotal, statCarry, quality);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ void jewelryCarriesScaledStatIntoFreshHistoryAndQualityLore() throws Exception {
JewelryCraftResult result = JewelryOutput.build(station, player);
assertNotNull(result);
assertSame(out, result.getItem());
assertEquals(Map.of(), GoldsmithProvenance.read(out));
assertEquals(80, result.getFinishedTotal());
assertEquals(90, result.getRecipePercent());
assertEquals(80, result.getHitPercent());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package net.tfminecraft.geminfusion.goldsmith;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.util.*;
import net.tfminecraft.geminfusion.InfusionMain;
import net.tfminecraft.geminfusion.PDCKeys;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
import org.junit.jupiter.api.*;
import org.mockbukkit.mockbukkit.MockBukkit;

class GoldsmithProvenanceTest {
InfusionMain previous;

@BeforeEach
void setup() {
MockBukkit.mock();
previous = InfusionMain.plugin;
InfusionMain.plugin = mock(InfusionMain.class);
when(InfusionMain.plugin.namespace()).thenReturn("geminfusion");
}

@AfterEach
void cleanup() {
InfusionMain.plugin = previous;
MockBukkit.unmock();
}

private static GoldsmithMaterial material(String path) {
GoldsmithMaterial material = mock(GoldsmithMaterial.class);
when(material.getPath()).thenReturn(path);
return material;
}

@Test
void stampsTheDepositedMaterialsNotTheRecipe() {
ItemStack ring = new ItemStack(Material.GOLD_INGOT);
LinkedHashMap<GoldsmithMaterial, Integer> deposited = new LinkedHashMap<>();
deposited.put(material("m.materials.shiny_gold"), 3);
deposited.put(material("m.materials.rough_gold"), 1);
deposited.put(material("m.materials.shiny_gold"), 2);

GoldsmithProvenance.stamp(ring, deposited);

assertEquals(
Map.of("m.materials.shiny_gold", 5, "m.materials.rough_gold", 1),
GoldsmithProvenance.read(ring));
}

@Test
void stampsAnEmptyListWhenNothingWasDeposited() {
ItemStack ring = new ItemStack(Material.GOLD_INGOT);
GoldsmithProvenance.stamp(ring, Map.of());
assertEquals(Map.of(), GoldsmithProvenance.read(ring));
}

@Test
void readsNothingFromUnstampedOrUnreadableItems() {
assertNull(GoldsmithProvenance.read(null));
assertNull(GoldsmithProvenance.read(new ItemStack(Material.AIR)));
assertNull(GoldsmithProvenance.read(new ItemStack(Material.GOLD_INGOT)));

ItemStack broken = new ItemStack(Material.GOLD_INGOT);
ItemMeta meta = broken.getItemMeta();
meta.getPersistentDataContainer()
.set(PDCKeys.goldsmithInputs(), PersistentDataType.STRING, "{not json");
broken.setItemMeta(meta);
assertNull(GoldsmithProvenance.read(broken));
}
}