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
32 changes: 19 additions & 13 deletions src/main/java/net/tfminecraft/cooking/cooking/PotReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,24 +99,30 @@ public void run() {
}

public FoodItem getMain() {
for(PlacedSlot slot : f.getActiveSlots().values()) {
ItemStack stack = slot.getCurrentItem();
if(stack == null) continue;
FoodItem item = FoodItem.fromItem(stack);
if(item == null) continue;
return item;
}
return null;
PlacedSlot slot = firstFoodSlot();
if (slot == null) return null;
return FoodItem.fromItem(slot.getCurrentItem());
}

/** Thickness lives on the first ingredient. Later pieces keep the model of what was held. */
public void setMain(FoodItem fi) {
for(PlacedSlot slot : f.getActiveSlots().values()) {
if (fi == null) return;
PlacedSlot slot = firstFoodSlot();
if (slot == null) return;
ItemStack stack = slot.getCurrentItem();
ItemStack updated = ItemUpdater.applyItemUpdate(stack, fi, f.getId());
if (updated == null) return;
slot.forceModel(updated);
}

private PlacedSlot firstFoodSlot() {
for (PlacedSlot slot : f.getActiveSlots().values()) {
ItemStack stack = slot.getCurrentItem();
if(stack == null) continue;
FoodItem item = FoodItem.fromItem(stack);
if(item == null) continue;
slot.forceModel(ItemUpdater.applyItemUpdate(stack, fi, f.getId()));
if (stack == null) continue;
if (FoodItem.fromItem(stack) == null) continue;
return slot;
}
return null;
}

private void handleParticles() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package net.tfminecraft.cooking.manager;

import net.tfminecraft.interactiblefurniture.furniture.data.DisplayData;

/**
* Bowl ingredient slots share one centered offset in the furniture definition.
* Spread input_1..input_5 around the bowl interior when the soup is poured.
*/
public final class BowlIngredientLayout {
static final float RADIUS = 0.12f;
private static final int INPUTS = 5;

private BowlIngredientLayout() {}

public static DisplayData offsetFor(String slotId) {
Integer index = inputIndex(slotId);
if (index == null) return null;
double angle = -Math.PI / 2d + index * (2d * Math.PI / INPUTS);
DisplayData data = new DisplayData();
data.setxPos((float) (Math.cos(angle) * RADIUS));
data.setzPos((float) (Math.sin(angle) * RADIUS));
return data;
}

static Integer inputIndex(String slotId) {
if (slotId == null || !slotId.startsWith("input_")) return null;
try {
int index = Integer.parseInt(slotId.substring("input_".length()));
if (index < 1 || index > INPUTS) return null;
return index - 1;
} catch (NumberFormatException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import net.tfminecraft.interactiblefurniture.events.FurnitureSlotItemAddEvent;
import net.tfminecraft.interactiblefurniture.furniture.Furniture;
import net.tfminecraft.interactiblefurniture.furniture.PlacedSlot;
import net.tfminecraft.interactiblefurniture.furniture.data.DisplayData;

public class PlateManager implements Listener{

Expand Down Expand Up @@ -144,7 +145,12 @@ public void addSoup(Player p, Furniture f, FoodItem soup, ItemStack base) {
for(Map.Entry<String, ItemStack> entry : map.entrySet()) {
if(f.hasActiveSlot(entry.getKey())) continue;
if (f.getType() == null || f.getType().getSlot(entry.getKey()) == null) continue;
f.getOrCreatePlacedSlot(entry.getKey()).forceModel(entry.getValue());
PlacedSlot placed = f.getOrCreatePlacedSlot(entry.getKey());
placed.forceModel(entry.getValue());
DisplayData spread = BowlIngredientLayout.offsetFor(entry.getKey());
if (spread != null) {
placed.applyDisplayData(spread);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
if (f.getType() == null || f.getType().getSlot("food_item") == null) return;
f.getOrCreatePlacedSlot("food_item").forceModel(base);
Expand Down
79 changes: 53 additions & 26 deletions src/main/java/net/tfminecraft/cooking/utils/Encoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Map;

import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

Expand All @@ -22,15 +23,15 @@ public static String getEncodedSlots(Furniture f) {

ItemMeta meta = item.getItemMeta();
int model = (meta != null && LegacyModelData.has(meta)) ? LegacyModelData.get(meta) : 0;
String itemModel = null;
if (meta != null && meta.hasItemModel() && meta.getItemModel() != null) {
itemModel = meta.getItemModel().toString();
}

if (!first) result.append(":");
first = false;

result.append(entry.getKey())
.append(".")
.append(item.getType().toString())
.append(".")
.append(model);
result.append(formatSlot(entry.getKey(), item.getType().toString(), model, itemModel));
}

return result.toString();
Expand All @@ -47,37 +48,63 @@ public static Map<String, ItemStack> decodeSlots(String encoded) {
for (String part : parts) {
if (part.isEmpty()) continue;

// Split into slot, type, model
String[] data = part.split("\\.");
if (data.length < 3) continue; // invalid format

String slotId = data[0]; // "slot_1"
String materialName = data[1]; // "IRON_INGOT"
String modelStr = data[2]; // "5"
ParsedSlot parsed = parseSlot(part);
if (parsed == null) continue;

// Convert material
Material mat = Material.matchMaterial(materialName);
if (mat == null) continue; // ignore invalid material names

int model;
try {
model = Integer.parseInt(modelStr);
} catch (NumberFormatException e) {
model = 0; // fallback
}
Material mat = Material.matchMaterial(parsed.material());
if (mat == null) continue;

// Build item
ItemStack item = new ItemStack(mat, 1);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
LegacyModelData.set(meta, model);
LegacyModelData.set(meta, parsed.customModelData());
if (parsed.itemModel() != null) {
NamespacedKey key = NamespacedKey.fromString(parsed.itemModel());
if (key != null) {
meta.setItemModel(key);
}
}
item.setItemMeta(meta);
}

// Add to result
result.put(slotId, item);
result.put(parsed.slotId(), item);
}

return result;
}

/** slot.MATERIAL.customModelData[.namespace~key]. ':' in the key is stored as '~'. */
static String formatSlot(String slotId, String material, int customModelData, String itemModel) {
StringBuilder result = new StringBuilder();
result.append(slotId).append('.').append(material).append('.').append(customModelData);
if (itemModel != null && !itemModel.isEmpty()) {
// Slot records are separated by ':'. Resource locations use ':' too.
result.append('.').append(itemModel.replace(':', '~'));
}
return result.toString();
}

static ParsedSlot parseSlot(String part) {
if (part == null || part.isEmpty()) return null;
String[] data = part.split("\\.", 4);
if (data.length < 3 || data[0].isEmpty() || data[1].isEmpty()) return null;
int model;
try {
model = Integer.parseInt(data[2]);
} catch (NumberFormatException e) {
model = 0;
}
String itemModel = null;
if (data.length >= 4 && !data[3].isEmpty()) {
int tilde = data[3].indexOf('~');
if (tilde > 0) {
itemModel = data[3].substring(0, tilde) + ":" + data[3].substring(tilde + 1);
} else {
itemModel = data[3];
}
}
return new ParsedSlot(data[0], data[1], model, itemModel);
}

record ParsedSlot(String slotId, String material, int customModelData, String itemModel) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.tfminecraft.cooking.manager;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

import net.tfminecraft.interactiblefurniture.furniture.data.DisplayData;

class BowlIngredientLayoutTest {

@Test
void ingredientsSitAroundTheBowlInsteadOfTheCenter() {
DisplayData[] placed = new DisplayData[5];
for (int i = 1; i <= 5; i++) {
placed[i - 1] = BowlIngredientLayout.offsetFor("input_" + i);
double radius = Math.hypot(placed[i - 1].getxPos(), placed[i - 1].getzPos());
assertEquals(BowlIngredientLayout.RADIUS, radius, 0.001);
}
for (int i = 0; i < placed.length; i++) {
for (int j = i + 1; j < placed.length; j++) {
double apart = Math.hypot(
placed[i].getxPos() - placed[j].getxPos(),
placed[i].getzPos() - placed[j].getzPos());
assertTrue(apart > 0.1, "input slots overlap at the center");
}
}
}

@Test
void soupLiquidAndHiddenFoodStayCentered() {
assertNull(BowlIngredientLayout.offsetFor("liquid"));
assertNull(BowlIngredientLayout.offsetFor("food_item"));
assertNull(BowlIngredientLayout.offsetFor("input_6"));
}
}
45 changes: 45 additions & 0 deletions src/test/java/net/tfminecraft/cooking/utils/SlotSnapshotTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package net.tfminecraft.cooking.utils;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import org.junit.jupiter.api.Test;

class SlotSnapshotTest {

@Test
void snapshotKeepsEachIngredientModel() {
String encoded = Encoder.formatSlot("input_1", "APPLE", 0, "tfmc_cooking:red_meat_steak_boiled")
+ ":"
+ Encoder.formatSlot("input_2", "APPLE", 0, "tfmc_cooking:pork_steak_boiled");

String[] parts = encoded.split(":", -1);
assertEquals(2, parts.length);
Encoder.ParsedSlot first = Encoder.parseSlot(parts[0]);
Encoder.ParsedSlot second = Encoder.parseSlot(parts[1]);

assertEquals("input_1", first.slotId());
assertEquals("tfmc_cooking:red_meat_steak_boiled", first.itemModel());
assertEquals("input_2", second.slotId());
assertEquals("tfmc_cooking:pork_steak_boiled", second.itemModel());
}

@Test
void snapshotKeepsItemModelKeysThatContainDots() {
Encoder.ParsedSlot parsed = Encoder.parseSlot(
Encoder.formatSlot("input_3", "CARROT", 4, "tfmc_cooking:item/carrot.boiled"));

assertEquals("CARROT", parsed.material());
assertEquals(4, parsed.customModelData());
assertEquals("tfmc_cooking:item/carrot.boiled", parsed.itemModel());
}

@Test
void olderSnapshotsWithoutAnItemModelStillParse() {
Encoder.ParsedSlot parsed = Encoder.parseSlot("input_1.POTATO.0");

assertEquals("POTATO", parsed.material());
assertEquals(0, parsed.customModelData());
assertNull(parsed.itemModel());
}
}