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
5 changes: 5 additions & 0 deletions src/main/java/net/tfminecraft/cooking/Cooking.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
import net.tfminecraft.cooking.oven.OvenCavityManager;
import net.tfminecraft.cooking.oven.OvenHandler;
import net.tfminecraft.cooking.oven.OvenLifecycleHandler;
import net.tfminecraft.cooking.utils.ItemRef;
import net.tfminecraft.cooking.utils.ItemsAdderReloadHook;
import net.tfminecraft.tlibs.itemscan.ItemScanService;
import net.tfminecraft.tlibs.character.Characters;

Expand Down Expand Up @@ -225,6 +227,7 @@ static void warnIfCharacterIdentityMissing(Logger logger) {
@Override
public void onDisable() {
if (incompatibleCharacterApi) return;
ItemRef.setCaching(false);
breweryBridge.close();
SaturationGuard.stop();
NutritionDrainTask.stop();
Expand Down Expand Up @@ -265,6 +268,7 @@ public void loadConfigs() {
}

public void reloadAll() {
ItemRef.clearCache();
loadConfigs();
HusbandryLifecycleListener.applyStatsRevision();
craftingManager.rebuildStations();
Expand Down Expand Up @@ -326,6 +330,7 @@ public void registerListeners() {
CropCustomCropsBridge.tryRegister(this);
getServer().getPluginManager().registerEvents(new CustomFishingBridge(), this);
CustomFishingBridge.tryRegister(this);
ItemsAdderReloadHook.registerIfPresent(this);
if (husbandryEnabled) {
getServer().getPluginManager().registerEvents(new HusbandryLifecycleListener(), this);
getServer().getPluginManager().registerEvents(new HusbandryCareListener(), this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;

import io.papermc.paper.persistence.PersistentDataContainerView;

import net.tfminecraft.cooking.manager.TagManager;
import net.tfminecraft.cooking.utils.Keys;
import net.tfminecraft.tlibs.itemscan.ItemScanHandler;
Expand All @@ -20,7 +21,8 @@ public boolean matches(ItemStack stack) {
if (stack == null || !Tag.ITEMS_FISHES.isTagged(stack.getType())) {
return false;
}
PersistentDataContainer data = stack.getItemMeta().getPersistentDataContainer();
// Runs on every scanned fish: read through the stack's data view instead of copying its whole meta.
PersistentDataContainerView data = stack.getPersistentDataContainer();
return isFood(data) ? SeafoodWholeItems.carriesCatchData(data) : stack.getType() == Material.COD;
}

Expand All @@ -44,7 +46,7 @@ public void update(Player player, Inventory inventory, int slot, ItemStack stack
}
}

private static boolean isFood(PersistentDataContainer data) {
private static boolean isFood(PersistentDataContainerView data) {
return data.has(Keys.FOOD_ID, PersistentDataType.STRING);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

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

import io.papermc.paper.persistence.PersistentDataContainerView;

import net.tfminecraft.cooking.item.FoodItem;
import net.tfminecraft.cooking.item.IngredientLineage;
import net.tfminecraft.cooking.loader.FoodLoader;
Expand Down Expand Up @@ -54,7 +55,7 @@ public static ItemStack build(ItemStack caught, VanillaFish fish, int quality) {
* True for a whole fish built by an older version on the caught CustomFishing item itself, so
* it still carries that item's per-catch data.
*/
static boolean carriesCatchData(PersistentDataContainer data) {
static boolean carriesCatchData(PersistentDataContainerView data) {
return CustomFishingCatalog.WHOLE_TYPE.equals(data.get(Keys.FOOD_ID, PersistentDataType.STRING))
&& data.has(Keys.CUSTOM_FISHING_ID, PersistentDataType.STRING)
&& !data.has(Keys.CLEAN_CATCH, PersistentDataType.BYTE);
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/net/tfminecraft/cooking/manager/TagManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ public class TagManager implements ItemScanHandler {

private static final int OFF_HAND_SLOT = 40;

// Runs on every scanned slot: read the key through the stack's data view instead of copying its whole meta.
@Override
public boolean matches(ItemStack stack) {
if (stack == null || stack.getType() == Material.AIR || !stack.hasItemMeta()) {
if (stack == null || stack.getType() == Material.AIR) {
return false;
}
ItemMeta meta = stack.getItemMeta();
return meta != null && meta.getPersistentDataContainer().has(Keys.FOOD_ID, PersistentDataType.STRING);
return stack.getPersistentDataContainer().has(Keys.FOOD_ID, PersistentDataType.STRING);
}

// Keep the existing legacy text representation, formatting, and exact-string comparisons.
Expand Down
80 changes: 78 additions & 2 deletions src/main/java/net/tfminecraft/cooking/utils/ItemRef.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,101 @@
package net.tfminecraft.cooking.utils;

import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;

import net.tfminecraft.tlibs.TLibs;
import net.tfminecraft.tlibs.objects.api.ItemAPI;
import net.tfminecraft.tlibs.objects.api.subapi.ItemCreator;

public class ItemRef {

/*
* Built ItemsAdder templates, one per ref, so the inventory scan's food rebuilds stop asking ItemsAdder
* for a fresh copy every time. Callers only ever get clones. Only refs that TLibs sends to ItemsAdder
* are kept: v. and modeled. builds are cheap, m. builds can roll random MMOItems stats, magic. has no
* creation path, and a prefix with a registered TLibs path handler (Cooking's own c., for one) may
* build something different each time. Failures (no item, air, or TLibs' dirt fallback when ItemsAdder
* is missing or has not loaded the item yet) are never kept, so they are retried like before.
* Only on while Cooking listens for ItemsAdder data loads (see ItemsAdderReloadHook). Cleared on
* /cooking reload, on every ItemsAdder data load, and when TLibs hands out a new creator.
*/
private static final Set<String> UNCACHED_PREFIXES = Set.of("v", "m", "modeled", "magic");
private static volatile boolean caching;
private static volatile Templates templates;

private record Templates(ItemCreator creator, Map<String, ItemStack> items) {}

public static ItemStack resolve(String ref) {
return TLibs.getItemAPI().getCreator().getItemFromPath(ref);
ItemCreator creator = TLibs.getItemAPI().getCreator();
Templates cache = cacheFor(ref, creator);
ItemStack cached = cache == null ? null : cache.items().get(ref);
if (cached != null) return cached.clone();
return build(ref, creator, cache);
}

public static ItemStack apply(String ref, ItemStack source) {
ItemStack template = resolve(ref);
ItemCreator creator = TLibs.getItemAPI().getCreator();
Templates cache = cacheFor(ref, creator);
ItemStack template = cache == null ? null : cache.items().get(ref);
if (template == null) template = build(ref, creator, cache);
if (template == null) return null;
ItemStack out = template.clone();
mergeMeta(out, source);
return out;
}

/** Drops every kept template; the next use of each ref builds it again. */
public static void clearCache() {
templates = null;
}

/** Turns template keeping on or off; either way the kept templates are dropped. */
public static void setCaching(boolean enabled) {
caching = enabled;
templates = null;
}

private static ItemStack build(String ref, ItemCreator creator, Templates cache) {
ItemStack built = creator.getItemFromPath(ref);
if (cache != null && isTemplate(built)) {
cache.items().putIfAbsent(ref, built.clone());
}
return built;
}

private static boolean isTemplate(ItemStack built) {
return built != null && !built.getType().isAir() && built.getType() != Material.DIRT;
}

/** The template cache for this ref, or null when the ref is not built from ItemsAdder. */
private static Templates cacheFor(String ref, ItemCreator creator) {
if (!caching || creator == null || !isItemsAdderRef(ref)) return null;
Templates current = templates;
if (current == null || current.creator() != creator) {
current = new Templates(creator, new ConcurrentHashMap<>());
templates = current;
}
return current;
}

// Mirrors the prefix routing in TLibs ItemCreator.getItemFromPath: everything else goes to ItemsAdder.
private static boolean isItemsAdderRef(String ref) {
if (ref == null) return false;
int dot = ref.indexOf('.');
if (dot <= 0 || dot == ref.length() - 1) return false;
String prefix = ref.substring(0, dot).toLowerCase(Locale.ROOT);
if (UNCACHED_PREFIXES.contains(prefix)) return false;
ItemAPI api = TLibs.getItemAPI();
return api.getPathHandler(prefix) == null;
}

// Keep the existing legacy text representation, formatting, and exact-string comparisons.
@SuppressWarnings("deprecation")
public static void mergeMeta(ItemStack out, ItemStack source) {
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/net/tfminecraft/cooking/utils/ItemUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@
import org.bukkit.persistence.PersistentDataType;

import java.util.List;
import java.util.regex.Pattern;

public class ItemUpdater {

// Same pattern String.replaceAll("§.", "") compiled on every lore line.
private static final Pattern COLOUR_CODE = Pattern.compile("§.");

/** Ages the FoodItem. Returns true if any TagStep changed (including nested sauce). */
public static boolean applyAging(FoodItem fi, long deltaSeconds) {
if (fi == null || deltaSeconds <= 0) return false;
Expand Down Expand Up @@ -160,7 +164,7 @@ public static boolean needsLoreRebuild(List<String> lore, boolean hasLoreIndex)
if (line == null) {
continue;
}
String stripped = line.replaceAll("§.", "");
String stripped = COLOUR_CODE.matcher(line).replaceAll("");
if (stripped.contains("Nutrition")) {
nutrition = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package net.tfminecraft.cooking.utils;

import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;

/**
* Turns on {@link ItemRef}'s ItemsAdder templates and clears them whenever ItemsAdder loads its items
* (startup and /iareload). Without ItemsAdder, or without that event, templates stay off.
* Cooking does not compile against ItemsAdder, so the event class is looked up from ItemsAdder's own loader.
*/
public final class ItemsAdderReloadHook implements Listener {
static final String LOAD_EVENT = "dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent";

private ItemsAdderReloadHook() {}

/** Returns true when ItemsAdder is installed and the hook was registered. */
public static boolean registerIfPresent(Plugin plugin) {
return registerIfPresent(plugin, LOAD_EVENT);
}

static boolean registerIfPresent(Plugin plugin, String eventClass) {
Plugin itemsAdder = plugin.getServer().getPluginManager().getPlugin("ItemsAdder");
if (itemsAdder == null) return false;
try {
Class<? extends Event> loadEvent =
Class.forName(eventClass, false, itemsAdder.getClass().getClassLoader()).asSubclass(Event.class);
plugin.getServer().getPluginManager().registerEvent(loadEvent, new ItemsAdderReloadHook(),
EventPriority.MONITOR, (listener, event) -> {
if (loadEvent.isInstance(event)) ItemRef.clearCache();
}, plugin);
} catch (ClassNotFoundException | RuntimeException | LinkageError e) {
// Without the reload signal a kept template could outlive /iareload, so keep none.
plugin.getLogger().warning("Cannot listen for " + eventClass + "; Cooking builds ItemsAdder items"
+ " fresh every time instead of keeping templates (" + e + ")");
return false;
}
ItemRef.setCaching(true);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.lone.itemsadder.api.Events;

import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;

/** Test stand-in for ItemsAdder's event; Cooking does not compile against ItemsAdder. */
public class ItemsAdderLoadDataEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();

@Override
public HandlerList getHandlers() {
return HANDLERS;
}

public static HandlerList getHandlerList() {
return HANDLERS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ public class FishingRuntimeCoverageTest {
ItemStack external=mock(ItemStack.class); when(external.getType()).thenReturn(Material.COD); when(external.hasItemMeta()).thenReturn(true);
assertNull(LegacyFishConversion.convert(null,external));
ItemMeta meta=mock(ItemMeta.class); when(external.getItemMeta()).thenReturn(meta);
when(meta.getPersistentDataContainer()).thenReturn(new ItemStack(Material.COD).getItemMeta().getPersistentDataContainer());
var externalData=new ItemStack(Material.COD).getItemMeta().getPersistentDataContainer();
when(meta.getPersistentDataContainer()).thenReturn(externalData); when(external.getPersistentDataContainer()).thenReturn(externalData);
assertNull(LegacyFishConversion.convert(null,external));
models.when(()->LegacyModelData.has(meta)).thenReturn(true); models.when(()->LegacyModelData.get(meta)).thenReturn(17);
when(external.getAmount()).thenReturn(7);
Expand Down
Loading
Loading