Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,14 @@ private void handleGemUnsocket(MMOItemRebuildEvent event, ItemStack oldItem, Ite
}
SocketRarityStore.remove(fixed, removedUuid);

if (rarityId != null) {
Gemstone gem = findGemFromSocketed(oldItem, removedUuid);
if (gem != null) {
String capturedRarity = rarityId;
Gemstone capturedGem = gem;
var beforeInventory = UnsocketInventorySnapshot.poll(player);
Bukkit.getScheduler().runTask(InfusionMain.plugin, () -> UnsocketedGemRestorer.restore(player,
capturedRarity, capturedGem, beforeInventory));
}
// Gems socketed before rarities were stored have none; restore them without one.
Gemstone gem = findGemFromSocketed(oldItem, removedUuid);
if (gem != null) {
String capturedRarity = rarityId;
Gemstone capturedGem = gem;
var beforeInventory = UnsocketInventorySnapshot.poll(player);
Bukkit.getScheduler().runTask(InfusionMain.plugin, () -> UnsocketedGemRestorer.restore(player,
capturedRarity, capturedGem, beforeInventory));
}
}

Expand Down
30 changes: 26 additions & 4 deletions src/main/java/net/tfminecraft/geminfusion/InfusedGemBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import net.Indyuce.mmoitems.stat.type.ItemStat;
import net.Indyuce.mmoitems.stat.type.NameData;
import net.Indyuce.mmoitems.stat.type.StatHistory;
import net.tfminecraft.geminfusion.goldsmith.InfusedGemValidator;

public final class InfusedGemBuilder {
private InfusedGemBuilder() {
Expand Down Expand Up @@ -57,12 +58,13 @@ public static void rollStats(MMOItem mmo, Gemstone gem, GemRarity rarity, int in
mmo.setData(ItemStats.SUCCESS_RATE, new DoubleData(Math.floor(Math.random() * maxChance) + 40));
}

/** A null rarity (unknown after a reset) leaves the rarity out of the name and lore. */
// Keep the existing legacy text representation, formatting, and exact-string comparisons.
@SuppressWarnings("deprecation")
public static void applyCosmetics(MMOItem mmo, Gemstone gem, GemRarity rarity) {
mmo.setData(ItemStats.DISPLAYED_TYPE, new StringData("Infused Gemstone"));

String infusedName = rarity.getName() + " Infused " + gem.getName();
String infusedName = (rarity == null ? "" : rarity.getName() + " ") + "Infused " + gem.getName();
StringData itemName = (StringData) mmo.getData(ItemStats.NAME);
if (itemName == null) {
itemName = new StringData(infusedName);
Expand All @@ -81,17 +83,37 @@ public static void applyCosmetics(MMOItem mmo, Gemstone gem, GemRarity rarity) {

List<String> loreList = new ArrayList<>();
loreList.add(ChatColor.GRAY + "Gemstone Type: " + gem.getSocketNameColour() + gem.getSocketColour());
loreList.add(ChatColor.GRAY + "Rarity: " + rarity.getName());
if (rarity != null) {
loreList.add(ChatColor.GRAY + "Rarity: " + rarity.getName());
}
mmo.setData(ItemStats.LORE, new StringListData(loreList));
}

public static ItemStack applyCosmeticsToItem(ItemStack item, Gemstone gem, GemRarity rarity) {
if (item == null || item.getType().isAir() || gem == null || rarity == null) {
if (item == null || item.getType().isAir() || gem == null) {
return item;
}
LiveMMOItem mmo = new LiveMMOItem(NBTItem.get(item));
applyCosmetics(mmo, gem, rarity);
return finalizeItem(mmo.newBuilder().build(), rarity.getId());
ItemStack rebuilt = finalizeItem(mmo.newBuilder().build(), rarity == null ? null : rarity.getId());
if (rebuilt != null) {
rebuilt.setAmount(item.getAmount());
}
return rebuilt;
}

/**
* Gives a reset infused gem (see {@link InfusedGemValidator#isReset}) its infused look back,
* keeping its rolled stats. Its rarity is kept only when the item still records it.
*/
public static ItemStack restoreReset(ItemStack item) {
if (!InfusedGemValidator.isReset(item)) {
return item;
}
NBTItem nbt = NBTItem.get(item);
Gemstone gem = ConfigLoader.findGemByMmoItem(nbt.getType(), nbt.getString("MMOITEMS_ITEM_ID"));
GemRarity rarity = ConfigLoader.findRarityById(GemRarityPdc.read(item));
return applyCosmeticsToItem(item, gem, rarity);
}

public static ItemStack buildInfusedGem(Gemstone gem, GemRarity rarity, int infusionAmount, Player player) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/net/tfminecraft/geminfusion/InfusionEvents.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.EquipmentSlot;
import net.tfminecraft.geminfusion.goldsmith.GoldsmithLog;
import net.tfminecraft.geminfusion.goldsmith.InfusedGemValidator;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;

Expand Down Expand Up @@ -61,6 +62,14 @@ public void addGemEvent(PlayerInteractEvent e) {
NBTItem nbt = NBTItem.get(item);
if(nbt.hasType() == false) return;
if(!nbt.getString("MMOITEMS_DISPLAYED_TYPE").equalsIgnoreCase("Blank Gemstone")) return;
if(InfusedGemValidator.isReset(item)) {
// Infusing again would wipe the roll; give the gem its infused look back instead.
ItemStack restored = InfusedGemBuilder.restoreReset(item);
if(restored == null) return;
p.getInventory().setItemInMainHand(restored);
p.sendMessage(ChatColor.YELLOW + "This gem already holds an infusion. Its glow returns.");
return;
}
for(Gemstone gem : ConfigLoader.loadedGems) {
if(nbt.getType().equalsIgnoreCase(gem.getMMOItemString().split("\\.")[0]) && nbt.getString("MMOITEMS_ITEM_ID").equalsIgnoreCase(gem.getMMOItem().getId())) {
if(gem.isLocationSpecific() == true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,29 @@
import org.bukkit.inventory.PlayerInventory;

import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmoitems.ItemStats;
import net.tfminecraft.geminfusion.goldsmith.InfusedGemValidator;

public final class UnsocketedGemRestorer {
private UnsocketedGemRestorer() {
}

/** A null or unknown rarityId restores the gem without a rarity. */
public static void restore(Player player, String rarityId, Gemstone gem, Map<Integer, ItemStack> beforeInventory) {
if (player == null || rarityId == null || gem == null) {
if (player == null || gem == null) {
return;
}
GemRarity rarity = ConfigLoader.findRarityById(rarityId);
if (rarity == null) {
return;
}

Integer slot = findTargetSlot(player, gem, beforeInventory);
if (slot == null) {
return;
}

PlayerInventory inventory = player.getInventory();
ItemStack item = inventory.getItem(slot);
inventory.setItem(slot, InfusedGemBuilder.applyCosmeticsToItem(item, gem, rarity));
ItemStack restored = InfusedGemBuilder.applyCosmeticsToItem(inventory.getItem(slot), gem, rarity);
if (restored != null) {
inventory.setItem(slot, restored);
}
}

private static Integer findTargetSlot(Player player, Gemstone gem, Map<Integer, ItemStack> beforeInventory) {
Expand Down Expand Up @@ -86,13 +86,6 @@ private static boolean matchesGem(ItemStack item, String gemType, String gemId)
}

private static boolean needsRestore(ItemStack item) {
NBTItem nbt = NBTItem.get(item);
if (nbt.hasTag(ItemStats.DISPLAYED_TYPE.getNBTPath())) {
String displayed = nbt.getString(ItemStats.DISPLAYED_TYPE.getNBTPath());
if (displayed != null && !displayed.isBlank() && !displayed.equalsIgnoreCase("Blank Gemstone")) {
return false;
}
}
return true;
return InfusedGemValidator.isReset(item);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import org.bukkit.inventory.ItemStack;

import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import net.tfminecraft.geminfusion.ConfigLoader;
import net.tfminecraft.geminfusion.GemStat;
import net.tfminecraft.geminfusion.Gemstone;

public final class InfusedGemValidator {
Expand All @@ -18,9 +21,24 @@ public static boolean isInfused(ItemStack item) {
if (item == null || item.getType().isAir()) return false;
NBTItem nbt = NBTItem.get(item);
if (!nbt.hasType()) return false;
if (!DISPLAY_INFUSED.equalsIgnoreCase(nbt.getString("MMOITEMS_DISPLAYED_TYPE"))) return false;
Gemstone gem = ConfigLoader.findGemByMmoItem(nbt.getType(), nbt.getString("MMOITEMS_ITEM_ID"));
return gem != null;
if (gem == null) return false;
String displayed = nbt.getString("MMOITEMS_DISPLAYED_TYPE");
if (DISPLAY_INFUSED.equalsIgnoreCase(displayed)) return true;
return DISPLAY_BLANK.equalsIgnoreCase(displayed) && hasInfusionStat(nbt, gem);
}

/**
* An infused gem that an MMOItems rebuild (e.g. unsocketing) turned back into a
* "Blank Gemstone": it keeps the rolled infusion stat, which blank templates never have.
*/
public static boolean isReset(ItemStack item) {
if (item == null || item.getType().isAir()) return false;
NBTItem nbt = NBTItem.get(item);
if (!nbt.hasType()) return false;
if (!DISPLAY_BLANK.equalsIgnoreCase(nbt.getString("MMOITEMS_DISPLAYED_TYPE"))) return false;
Gemstone gem = ConfigLoader.findGemByMmoItem(nbt.getType(), nbt.getString("MMOITEMS_ITEM_ID"));
return gem != null && hasInfusionStat(nbt, gem);
}

public static boolean isGemstoneCandidate(ItemStack item) {
Expand All @@ -33,4 +51,14 @@ public static boolean isGemstoneCandidate(ItemStack item) {
}
return ConfigLoader.findGemByMmoItem(nbt.getType(), nbt.getString("MMOITEMS_ITEM_ID")) != null;
}

private static boolean hasInfusionStat(NBTItem nbt, Gemstone gem) {
for (GemStat block : gem.getStats()) {
ItemStat<?, ?> stat = MMOItems.plugin.getStats().get(block.getStatId().toUpperCase());
if (stat == null) continue;
String path = stat.getNBTPath();
if (nbt.hasTag(path) && nbt.getDouble(path) != 0) return true;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
return false;
}
}
72 changes: 71 additions & 1 deletion src/test/java/net/tfminecraft/geminfusion/GemOutputTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ void cosmeticsUpdateNameHistoryLoreAndRarityWithoutChangingStats() {
assertEquals("Rare Infused Ruby", original.getString());
verify(output).setStatHistory(ItemStats.NAME, history);
verify(output, times(2)).setData(eq(ItemStats.LORE), any(StringListData.class));
MMOItem unknown = mock(MMOItem.class);
InfusedGemBuilder.applyCosmetics(unknown, gem, null);
verify(unknown).replaceData(eq(ItemStats.NAME), name.capture());
assertEquals("Infused Ruby", name.getValue().getString());
var lore = ArgumentCaptor.forClass(StringListData.class);
verify(unknown).setData(eq(ItemStats.LORE), lore.capture());
assertEquals(1, lore.getValue().getList().size());
}

@Test
Expand All @@ -166,13 +173,18 @@ void buildsAndRebuildsGemWithCosmeticsGlintAndPdc() {
assertSame(result, InfusedGemBuilder.buildInfusedGem(template, rarity, 3, player));
assertEquals("rare", GemRarityPdc.read(result));
assertTrue(result.getItemMeta().hasItemFlag(ItemFlag.HIDE_ENCHANTS));
blank.setAmount(2);
assertSame(result, InfusedGemBuilder.applyCosmeticsToItem(blank, gem, rarity));
assertEquals(2, result.getAmount());
assertSame(result, InfusedGemBuilder.applyCosmeticsToItem(blank, gem, null));
}
try (var constructed = live(m -> when(m.newBuilder().build()).thenReturn(null))) {
assertNull(InfusedGemBuilder.applyCosmeticsToItem(blank, gem, rarity));
}
assertNull(InfusedGemBuilder.applyCosmeticsToItem(null, gem, rarity));
ItemStack air = new ItemStack(Material.AIR);
assertSame(air, InfusedGemBuilder.applyCosmeticsToItem(air, gem, rarity));
assertSame(blank, InfusedGemBuilder.applyCosmeticsToItem(blank, null, rarity));
assertSame(blank, InfusedGemBuilder.applyCosmeticsToItem(blank, gem, null));
assertNull(InfusedGemBuilder.finalizeItem(null, "rare"));
assertSame(air, InfusedGemBuilder.finalizeItem(air, "rare"));
}
Expand Down Expand Up @@ -202,6 +214,64 @@ void infusedValidatorRecognizesOnlyConfiguredInfusedGemsAndCandidates() {
assertTrue(InfusedGemValidator.isGemstoneCandidate(item));
}

@Test
void blankGemKeepingItsInfusionStatCountsAsInfusedAndReset() throws Exception {
ItemStack item = tagged(Material.DIAMOND);
String path = ItemStats.ATTACK_DAMAGE.getNBTPath();
gem.addStat(stat("common", "MISSING", 1));
gem.addStat(stat("rare", "ATTACK_DAMAGE", 2));
when(MMOItems.plugin.getStats().get("MISSING")).thenReturn(null);
when(nbt.getString("MMOITEMS_DISPLAYED_TYPE")).thenReturn("Blank Gemstone");
assertFalse(InfusedGemValidator.isInfused(item));
assertFalse(InfusedGemValidator.isReset(item));
when(nbt.hasTag(path)).thenReturn(true);
assertFalse(InfusedGemValidator.isReset(item));
when(nbt.getDouble(path)).thenReturn(4.1);
assertTrue(InfusedGemValidator.isInfused(item));
assertTrue(InfusedGemValidator.isReset(item));
when(nbt.getString("MMOITEMS_DISPLAYED_TYPE")).thenReturn("Sword");
assertFalse(InfusedGemValidator.isInfused(item));
assertFalse(InfusedGemValidator.isReset(item));
when(nbt.getString("MMOITEMS_DISPLAYED_TYPE")).thenReturn("Infused Gemstone");
assertFalse(InfusedGemValidator.isReset(item));
when(nbt.getString("MMOITEMS_DISPLAYED_TYPE")).thenReturn("Blank Gemstone");
when(nbt.getString("MMOITEMS_ITEM_ID")).thenReturn("OTHER");
assertFalse(InfusedGemValidator.isReset(item));
when(nbt.hasType()).thenReturn(false);
assertFalse(InfusedGemValidator.isReset(item));
assertFalse(InfusedGemValidator.isReset(null));
assertFalse(InfusedGemValidator.isReset(new ItemStack(Material.AIR)));
}

@Test
void restoreResetRebuildsOnlyResetGemsKeepingKnownRarityAndAmount() throws Exception {
ItemStack item = tagged(Material.DIAMOND), withRarity = tagged(Material.DIAMOND);
String path = ItemStats.ATTACK_DAMAGE.getNBTPath();
gem.addStat(stat("rare", "ATTACK_DAMAGE", 2));
when(nbt.getString("MMOITEMS_DISPLAYED_TYPE")).thenReturn("Blank Gemstone");
assertSame(item, InfusedGemBuilder.restoreReset(item));
when(nbt.hasTag(path)).thenReturn(true);
when(nbt.getDouble(path)).thenReturn(4.1);
ConfigLoader.loadedRarities.add(rarity);
item.setAmount(3);
try (var constructed = live(m -> when(m.newBuilder().build()).thenReturn(withRarity))) {
GemRarityPdc.write(item, "rare");
assertSame(withRarity, InfusedGemBuilder.restoreReset(item));
assertEquals("rare", GemRarityPdc.read(withRarity));
assertEquals(3, withRarity.getAmount());
verify(constructed.constructed().get(0))
.replaceData(eq(ItemStats.NAME), argThat(d -> "Rare Infused Ruby".equals(((StringData) d).getString())));
}
ItemStack unknown = tagged(Material.DIAMOND), withoutRarity = tagged(Material.DIAMOND);
try (var constructed = live(m -> when(m.newBuilder().build()).thenReturn(withoutRarity))) {
assertSame(withoutRarity, InfusedGemBuilder.restoreReset(unknown));
assertNull(GemRarityPdc.read(withoutRarity));
verify(constructed.constructed().get(0))
.replaceData(eq(ItemStats.NAME), argThat(d -> "Infused Ruby".equals(((StringData) d).getString())));
}
ConfigLoader.loadedRarities.clear();
}

@Test
void jewelryRejectsMissingInputsInvalidGemsAndUnreadableStats() {
GoldsmithStation station = mock(GoldsmithStation.class);
Expand Down
28 changes: 28 additions & 0 deletions src/test/java/net/tfminecraft/geminfusion/InfusionEventsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.lumine.mythic.lib.api.item.NBTItem;
import java.util.*;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.tfminecraft.geminfusion.goldsmith.InfusedGemValidator;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.*;
Expand Down Expand Up @@ -46,6 +47,7 @@ void setup() {
when(block.getLocation()).thenReturn(location);
when(block.getType()).thenReturn(Material.ENCHANTING_TABLE);
when(hand.getAmount()).thenReturn(5);
when(hand.getType()).thenReturn(Material.AMETHYST_SHARD);
when(nbt.hasType()).thenReturn(true);
when(nbt.getType()).thenReturn("GEM_STONE");
when(nbt.getString("MMOITEMS_DISPLAYED_TYPE")).thenReturn("Blank Gemstone");
Expand Down Expand Up @@ -144,6 +146,32 @@ void configuredStationAndGemLocationsMustMatch() {
location.clone().add(.5, 1, .5), events.currentStations.getFirst().getParticleLocation());
}

@Test
void resetInfusedGemIsRestoredInHandInsteadOfInfusedAgain() {
ItemStack restored = mock(ItemStack.class);
try (var valid = mockStatic(InfusedGemValidator.class);
var builder = mockStatic(InfusedGemBuilder.class)) {
valid.when(() -> InfusedGemValidator.isReset(hand)).thenReturn(true);
builder.when(() -> InfusedGemBuilder.restoreReset(hand)).thenReturn(restored);
PlayerInteractEvent click = event(Action.RIGHT_CLICK_BLOCK, EquipmentSlot.HAND);
events.addGemEvent(click);
assertTrue(click.isCancelled());
}
verify(inventory).setItemInMainHand(restored);
verify(player).sendMessage(contains("already holds an infusion"));
assertTrue(events.currentStations.isEmpty());
// A failed rebuild keeps the original gem and does not infuse it.
try (var valid = mockStatic(InfusedGemValidator.class);
var builder = mockStatic(InfusedGemBuilder.class)) {
valid.when(() -> InfusedGemValidator.isReset(hand)).thenReturn(true);
events.addGemEvent(event(Action.RIGHT_CLICK_BLOCK, EquipmentSlot.HAND));
}
verify(inventory, times(1)).setItemInMainHand(any());
verify(player, times(1)).sendMessage(contains("already holds an infusion"));
assertTrue(events.currentStations.isEmpty());
verify(hand, never()).setAmount(anyInt());
}

@Test
void startedAndFullStationsRejectFurtherGems() {
InfusionBlock s = station(location, 1, 1);
Expand Down
Loading