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
2 changes: 2 additions & 0 deletions src/main/java/net/tfminecraft/cooking/Cooking.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import net.tfminecraft.cooking.loader.PermissionEffectsLoader;
import net.tfminecraft.cooking.loader.CompositionConfigLoader;
import net.tfminecraft.cooking.loader.QualityConfigLoader;
import net.tfminecraft.cooking.manager.BundleFoodStacker;
import net.tfminecraft.cooking.manager.CommandManager;
import net.tfminecraft.cooking.manager.ConversionManager;
import net.tfminecraft.cooking.manager.CookingManager;
Expand Down Expand Up @@ -262,6 +263,7 @@ public void registerListeners() {
getServer().getPluginManager().registerEvents(new HusbandryMountListener(), this);
getServer().getPluginManager().registerEvents(new HusbandryDamageListener(), this);
getServer().getPluginManager().registerEvents(new ConversionManager(), this);
getServer().getPluginManager().registerEvents(new BundleFoodStacker(), this);
getServer().getPluginManager().registerEvents(new RegenBlocker(), this);
getServer().getPluginManager().registerEvents(new SaturationGuard(), this);
getServer().getPluginManager().registerEvents(new NutritionLifecycleListener(), this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,13 @@ public static void touchLastUpdate(Furniture furniture) {
}

public static void tickAge(Furniture furniture) {
tickAge(furniture, System.currentTimeMillis());
}

static void tickAge(Furniture furniture, long now) {
if (!TYPE_MILK.equalsIgnoreCase(getType(furniture)) || isEmpty(furniture)) {
return;
}
long now = System.currentTimeMillis();
long last = getLastUpdate(furniture);
long elapsedMs = now - last;
if (elapsedMs <= 0) {
Expand Down
149 changes: 149 additions & 0 deletions src/main/java/net/tfminecraft/cooking/manager/BundleFoodStacker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package net.tfminecraft.cooking.manager;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import org.bukkit.Bukkit;
import org.bukkit.Tag;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BundleMeta;

import net.tfminecraft.cooking.Cooking;
import net.tfminecraft.cooking.item.FoodItem;
import net.tfminecraft.cooking.item.tag.TagTrack;
import net.tfminecraft.cooking.utils.InventoryAdder;

/**
* Aging foods carry their own clock, so two pieces of the same meat never match exactly and a
* bundle keeps them apart. After a click that may have put food into a bundle, equal foods in it
* are merged the way inventories already stack them. Each merge keeps the more aged copy's data,
* so putting old food in with fresh food can never make it fresher.
*/
public class BundleFoodStacker implements Listener {

private static final String FRESHNESS = "freshness";

private final Consumer<Runnable> nextTick;

public BundleFoodStacker() {
this(task -> Bukkit.getScheduler().runTask(Cooking.plugin, task));
}

BundleFoodStacker(Consumer<Runnable> nextTick) {
this.nextTick = nextTick;
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onClick(InventoryClickEvent e) {
if (!isBundle(e.getCursor()) && !isBundle(e.getCurrentItem())) return;

HumanEntity player = e.getWhoClicked();
Inventory clicked = e.getClickedInventory();
int slot = e.getSlot();
// The bundle changes after this event, so merge on the next tick. Read the cursor and the
// slot again then: the player may have moved things in between.
nextTick.accept(() -> stackBundles(player, clicked, slot));
}

static void stackBundles(HumanEntity player, Inventory clicked, int slot) {
ItemStack cursor = stack(player.getItemOnCursor());
if (cursor != null) player.setItemOnCursor(cursor);

if (clicked == null || !inView(player.getOpenInventory(), clicked)) return;
ItemStack inSlot = stack(clicked.getItem(slot));
if (inSlot != null) clicked.setItem(slot, inSlot);
}

private static boolean inView(InventoryView view, Inventory inv) {
return view.getTopInventory() == inv || view.getBottomInventory() == inv;
}

/** The bundle with its foods merged, or null when it is not a bundle or nothing merged. */
static ItemStack stack(ItemStack bundle) {
if (!isBundle(bundle) || !(bundle.getItemMeta() instanceof BundleMeta meta)) return null;
List<ItemStack> merged = merge(meta.getItems());
if (merged == null) return null;

ItemStack out = bundle.clone();
meta.setItems(merged);
out.setItemMeta(meta);
return out;
}

/** Bundle contents with equal foods merged, keeping order, or null when nothing merged. */
static List<ItemStack> merge(List<ItemStack> items) {
List<ItemStack> out = new ArrayList<>();
boolean changed = false;

for (ItemStack item : items) {
FoodItem food = FoodItem.fromItem(item);
int amount = item.getAmount();

for (int i = 0; i < out.size() && food != null && amount > 0; i++) {
ItemStack target = out.get(i);
int room = target.getMaxStackSize() - target.getAmount();
if (room <= 0 || !sameFood(food, item, target)) continue;

int add = Math.min(room, amount);
ItemStack kept = moreAged(target, item).clone();
kept.setAmount(target.getAmount() + add);
out.set(i, kept);
amount -= add;
changed = true;
}

if (amount > 0) {
ItemStack rest = item.clone();
rest.setAmount(amount);
out.add(rest);
}
}
return changed ? out : null;
}

private static boolean sameFood(FoodItem food, ItemStack item, ItemStack other) {
if (item.getType() != other.getType()) return false;
FoodItem otherFood = FoodItem.fromItem(other);
return otherFood != null
&& InventoryAdder.equalsFood(food, otherFood)
&& ConversionManager.sameApartFromAging(item, other);
}

/**
* Equal foods age at the same rate, so whichever is further along now is the older one.
* Freshness decides, so food never gets fresher; the other aging tracks, such as warmth, only
* break a tie.
*/
static ItemStack moreAged(ItemStack a, ItemStack b) {
double[] ageA = age(a);
double[] ageB = age(b);
int order = Double.compare(ageA[0], ageB[0]);
if (order == 0) order = Double.compare(ageA[1], ageB[1]);
return order >= 0 ? a : b;
}

/** How far the food has aged by now: freshness first, then every other aging track. */
private static double[] age(ItemStack item) {
FoodItem food = FoodItem.fromItem(item);
food.updateAge();
double[] age = new double[2];
for (TagTrack track : food.getTagTracks()) {
if (!track.isAgeable()) continue;
int index = FRESHNESS.equals(track.getId()) ? 0 : 1;
age[index] += track.getValue() + food.getAgeRemainder(track.getId());
}
return age;
}

private static boolean isBundle(ItemStack item) {
return item != null && Tag.ITEMS_BUNDLES.isTagged(item.getType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import net.tfminecraft.cooking.util.LegacyModelData;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.bukkit.Bukkit;
Expand Down Expand Up @@ -172,28 +174,33 @@ public void onOpen(InventoryOpenEvent e) {
int topSize = top.getSize();
int total = topSize + bottom.getSize();

for (int i = total - 1; i >= 0; i--) {
ItemStack a = getSlot(i, top, bottom, topSize);
if (a == null) continue;

FoodItem fa = FoodItem.fromItem(a);
if (fa == null) continue;

for (int j = 0; j < total; j++) {
if (i == j) continue;

ItemStack b = getSlot(j, top, bottom, topSize);
if (b == null) continue;
ItemStack[] items = new ItemStack[total];
FoodItem[] foods = new FoodItem[total];
for (int i = 0; i < total; i++) {
items[i] = getSlot(i, top, bottom, topSize);
foods[i] = items[i] == null ? null : FoodItem.fromItem(items[i]);
}

FoodItem fb = FoodItem.fromItem(b);
if (fb == null) continue;
// Equal foods all take the data of the most aged one, so opening a chest never makes food
// fresher. Ties go to the first slot.
for (int i = 0; i < total; i++) {
if (foods[i] == null) continue;

List<Integer> group = new ArrayList<>(List.of(i));
ItemStack oldest = items[i];
for (int j = i + 1; j < total; j++) {
if (foods[j] == null) continue;
if (!InventoryAdder.equalsFood(foods[i], foods[j]) || !sameApartFromAging(items[i], items[j])) continue;
group.add(j);
foods[j] = null;
oldest = BundleFoodStacker.moreAged(oldest, items[j]);
}

if (InventoryAdder.equalsFood(fa, fb) && sameApartFromAging(a, b)) {
ItemStack clone = b.clone();
clone.setAmount(a.getAmount());
setSlot(i, clone, top, bottom, topSize);
break;
}
for (int k : group) {
if (oldest.isSimilar(items[k])) continue;
ItemStack clone = oldest.clone();
clone.setAmount(items[k].getAmount());
setSlot(k, clone, top, bottom, topSize);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,16 @@ void milkAgingUsesElapsedWholeSecondsAndCarriesScaledFractions() {
fill("water", 2); LiquidContainerState.tickAge(barrel.furniture); assertFalse(barrel.variables.containsKey(LiquidContainerState.VAR_LAST_UPDATE));
fill("milk", 0); LiquidContainerState.tickAge(barrel.furniture); assertFalse(barrel.variables.containsKey(LiquidContainerState.VAR_LAST_UPDATE));
fill("milk", 2); LiquidContainerState.setMilkSnapshot(barrel.furniture, 3, 7, "Cow");
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, System.currentTimeMillis() + 100_000L);
LiquidContainerState.tickAge(barrel.furniture); assertEquals(7, LiquidContainerState.getDairyFreshness(barrel.furniture));
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, System.currentTimeMillis());
LiquidContainerState.tickAge(barrel.furniture); assertEquals(7, LiquidContainerState.getDairyFreshness(barrel.furniture));
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, System.currentTimeMillis() - 2_000L);
LiquidContainerState.tickAge(barrel.furniture); assertEquals(7, LiquidContainerState.getDairyFreshness(barrel.furniture));
long now = 1_000_000L;
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, now + 100_000L);
LiquidContainerState.tickAge(barrel.furniture, now); assertEquals(7, LiquidContainerState.getDairyFreshness(barrel.furniture));
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, now - 500L);
LiquidContainerState.tickAge(barrel.furniture, now); assertEquals(7, LiquidContainerState.getDairyFreshness(barrel.furniture));
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, now - 2_000L);
LiquidContainerState.tickAge(barrel.furniture, now); assertEquals(7, LiquidContainerState.getDairyFreshness(barrel.furniture));
assertEquals(0.5, (Double) barrel.variables.get(LiquidContainerState.VAR_FRESHNESS_REMAINDER), 0.001);
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, System.currentTimeMillis() - 2_000L);
LiquidContainerState.tickAge(barrel.furniture); assertEquals(8, LiquidContainerState.getDairyFreshness(barrel.furniture));
barrel.variables.put(LiquidContainerState.VAR_LAST_UPDATE, now - 2_000L);
LiquidContainerState.tickAge(barrel.furniture, now); assertEquals(8, LiquidContainerState.getDairyFreshness(barrel.furniture));
assertEquals(0.0, (Double) barrel.variables.get(LiquidContainerState.VAR_FRESHNESS_REMAINDER), 0.001);
}

Expand Down
Loading
Loading