From ba59dc6de17bda41f180b78ad60f87c2938c2e18 Mon Sep 17 00:00:00 2001 From: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Date: Fri, 25 Sep 2026 07:41:26 +0000 Subject: [PATCH 1/3] fix: award crafting XP only when the craft succeeds Branding a station called giveXP before checking materials and hits. A failed attempt keeps the station's materials, so each branding click on an unfinished station granted the materials' profession XP again. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../objects/crafting/CraftingStation.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java b/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java index 1833023..5766716 100644 --- a/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java +++ b/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java @@ -253,8 +253,12 @@ public StationFeedback craft(Player p) { public StationFeedback craft(Player p, Double forcedQualityPercent) { stats = CraftStatCalculator.compute(recipe, currentMaterials); - giveXP(p); - return createItem(p, forcedQualityPercent); + StationFeedback f = createItem(p, forcedQualityPercent); + // Failed attempts keep the station's materials, so paying XP before the checks let every retry pay again. + if (f == StationFeedback.SUCCESS) { + giveXP(p); + } + return f; } private void giveXP(Player p) { From b72c0b4702c1a7a111444e35c29ee30fdd9e794c Mon Sep 17 00:00:00 2001 From: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Date: Fri, 25 Sep 2026 07:49:31 +0000 Subject: [PATCH 2/3] fix: skip malformed xp values instead of throwing XP is now paid after the item drops. An exception there would skip the caller's station cleanup and let the player brand the station again for a second item. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../objects/crafting/CraftingStation.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java b/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java index 5766716..fe597ea 100644 --- a/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java +++ b/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java @@ -278,14 +278,14 @@ private void giveXP(Player p) { Ingredient ingredient = IngredientLoader.getByString(mId); // Assuming you have a method like this if (ingredient != null && ingredient.getIngredientData().hasXP()) { String raw = ingredient.getIngredientData().getXP(); - xpPerUnit = Double.parseDouble(raw.split("\\(")[1].replace(")", "")); + xpPerUnit = parseXPAmount(raw); skill = raw.split("\\(")[0]; // Assuming you store "agriculturist" here } } else if (type.equalsIgnoreCase("alloy")) { Alloy alloy = AlloyManager.getAlloyById(mId); // Likewise for alloy if (alloy != null && alloy.getData().hasXP()) { String raw = alloy.getData().getXP(); - xpPerUnit = Double.parseDouble(raw.split("\\(")[1].replace(")", "")); + xpPerUnit = parseXPAmount(raw); skill = raw.split("\\(")[0]; // Assuming you store "agriculturist" here } } @@ -307,6 +307,16 @@ private void giveXP(Player p) { } + // XP is paid after the item drops, so a bad value must not throw and leave the station uncleared. + private double parseXPAmount(String raw) { + try { + return Double.parseDouble(raw.split("\\(")[1].replace(")", "")); + } catch (RuntimeException e) { + Bukkit.getLogger().warning("AC: Invalid xp value '" + raw + "', expected skill(amount)"); + return 0.0; + } + } + private boolean checkItems(Player p) { boolean complete = true; for(IngredientType t : types.keySet()) { From 3643cc93aa86dc689fe357372cc6d06a9a49ba9c Mon Sep 17 00:00:00 2001 From: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Date: Fri, 25 Sep 2026 08:06:49 +0000 Subject: [PATCH 3/3] fix: require the full skill(amount) format for xp values Values like smithing(5)(x) were read as smithing(5). They are now skipped with a warning, and only a plain skill name can reach the console exp command. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../objects/crafting/CraftingStation.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java b/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java index fe597ea..0b23ae2 100644 --- a/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java +++ b/src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.lang.WordUtils; import org.bukkit.Bukkit; @@ -261,6 +263,9 @@ public StationFeedback craft(Player p, Double forcedQualityPercent) { return f; } + // skill(amount), e.g. crafter(2.0) + private static final Pattern XP_FORMAT = Pattern.compile("([A-Za-z0-9_-]+)\\((\\d+(?:\\.\\d+)?)\\)"); + private void giveXP(Player p) { // Map of skill name -> total XP to give Map xpBySkill = new HashMap<>(); @@ -271,26 +276,31 @@ private void giveXP(Player p) { String mId = split[1]; int amount = currentMaterials.get(s); - double xpPerUnit = 0.0; - String skill = null; + String raw = null; if (type.equalsIgnoreCase("ingredient")) { Ingredient ingredient = IngredientLoader.getByString(mId); // Assuming you have a method like this if (ingredient != null && ingredient.getIngredientData().hasXP()) { - String raw = ingredient.getIngredientData().getXP(); - xpPerUnit = parseXPAmount(raw); - skill = raw.split("\\(")[0]; // Assuming you store "agriculturist" here + raw = ingredient.getIngredientData().getXP(); } } else if (type.equalsIgnoreCase("alloy")) { Alloy alloy = AlloyManager.getAlloyById(mId); // Likewise for alloy if (alloy != null && alloy.getData().hasXP()) { - String raw = alloy.getData().getXP(); - xpPerUnit = parseXPAmount(raw); - skill = raw.split("\\(")[0]; // Assuming you store "agriculturist" here + raw = alloy.getData().getXP(); } } + if (raw == null) continue; + + // XP is paid after the item drops, so a bad value must be skipped rather than throw and leave the station uncleared. + Matcher m = XP_FORMAT.matcher(raw.trim()); + if (!m.matches()) { + Bukkit.getLogger().warning("AC: Invalid xp value '" + raw + "', expected skill(amount)"); + continue; + } + String skill = m.group(1); + double xpPerUnit = Double.parseDouble(m.group(2)); - if (skill != null && xpPerUnit > 0) { + if (xpPerUnit > 0) { double totalXP = xpPerUnit * amount; xpBySkill.put(skill, xpBySkill.getOrDefault(skill, 0.0) + totalXP); } @@ -307,16 +317,6 @@ private void giveXP(Player p) { } - // XP is paid after the item drops, so a bad value must not throw and leave the station uncleared. - private double parseXPAmount(String raw) { - try { - return Double.parseDouble(raw.split("\\(")[1].replace(")", "")); - } catch (RuntimeException e) { - Bukkit.getLogger().warning("AC: Invalid xp value '" + raw + "', expected skill(amount)"); - return 0.0; - } - } - private boolean checkItems(Player p) { boolean complete = true; for(IngredientType t : types.keySet()) {