From 0baa1b8b28a4f405723b818adb0bf19082e66177 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 4 Jul 2025 15:17:48 +0200 Subject: [PATCH 01/11] linear cost util functions --- .../xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index 9220ee3..f60581f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -203,4 +203,43 @@ object AnvilXpUtil { return rightValue + illegalPenalty } + /** + * Calculate the maximum level reachable with this amount of `xp` + * This is equivalent of the displayed level on client + * @author provided by kFor + */ + fun calculateLevelForXp(xp: Int): Int { + return when { + xp <= 352 -> (Math.sqrt((xp + 9).toDouble()) - 3).toInt() + xp <= 1507 -> { + val inner = (2.0 / 5.0) * (xp - 7839.0 / 40.0) + (81.0 / 10.0 + Math.sqrt(inner)).toInt() + } + + else -> { + val inner = (2.0 / 9.0) * (xp - 54215.0 / 72.0) + (325.0 / 18.0 + Math.sqrt(inner)).toInt() + } + } + } + + /** + * Calculate the minimum level necessary to have at least `xp` + */ + fun calculateMinimumLevelForXp(xp: Int): Int { + return calculateLevelForXp(xp - 1) + 1 + } + + /** + * Calculate the minimum amount of xp necessary to reach `level` + * @author provided by kFor + */ + fun calculateXpForLevel(level: Int): Int { + return when { + level <= 16 -> (level * level + 6 * level) + level <= 31 -> (2.5 * level * level - 40.5 * level + 360).toInt() + else -> (4.5 * level * level - 162.5 * level + 2220).toInt() + } + } + } \ No newline at end of file From ec4351e70d89a0aa64d3d20ddf99c7a97d32a169 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 4 Jul 2025 15:24:35 +0200 Subject: [PATCH 02/11] custom craft linear cost config values --- .../config/global/CustomRecipeConfigGui.java | 4 +- .../elements/CustomRecipeSubSettingGui.java | 4 +- .../cuanvil/listener/AnvilResultListener.kt | 2 +- .../cuanvil/listener/PrepareAnvilListener.kt | 2 +- .../cuanvil/recipe/AnvilCustomRecipe.kt | 76 +++++++++++++------ 5 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java index feaa8dd..ee8895c 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java @@ -63,7 +63,7 @@ public class CustomRecipeConfigGui extends MappedGuiListConfigGui Date: Fri, 4 Jul 2025 15:48:59 +0200 Subject: [PATCH 03/11] linear cost logic --- .../cuanvil/listener/AnvilResultListener.kt | 23 ++++++++++++++----- .../cuanvil/listener/PrepareAnvilListener.kt | 9 ++++---- .../cuanvil/recipe/AnvilCustomRecipe.kt | 15 ++++++++++++ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 27c99a2..8e37169 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -130,10 +130,17 @@ class AnvilResultListener : Listener { if (recipe.leftItem == null) return // in case it changed val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) - val xpCost = amount * recipe.levelCostPerCraft + val xpCost = recipe.determineCost(amount, leftItem, output) + val finalCost = + if (recipe.removeExactXp) xpCost + else AnvilXpUtil.calculateLevelForXp(xpCost) - CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $xpCost, level: ${player.level}, result: ${player.level < xpCost}") - if ((player.gameMode != GameMode.CREATIVE) && (player.level < xpCost)) return + CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $finalCost, level: ${player.level}, result: ${player.totalExperience < finalCost} ${player.level < finalCost}") + if (player.gameMode != GameMode.CREATIVE){ + if(recipe.removeExactXp){ + if(player.totalExperience < finalCost) return + }else if(player.level < finalCost) return + } // We give the item manually // But first we check if we should give the item @@ -142,7 +149,7 @@ class AnvilResultListener : Listener { // Handle not creative middle click... if (event.click != ClickType.MIDDLE && - !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, xpCost) + !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, finalCost, recipe.removeExactXp) ) return // Finally, we add the item to the player @@ -157,7 +164,7 @@ class AnvilResultListener : Listener { event: InventoryClickEvent, recipe: AnvilCustomRecipe, inventory: AnvilInventory, player: Player, leftItem: ItemStack, rightItem: ItemStack?, - amount: Int, xpCost: Int + amount: Int, xpCost: Int, linearCost: Boolean = false ): Boolean { // We remove what should be removed if (rightItem != null) { @@ -171,7 +178,11 @@ class AnvilResultListener : Listener { inventory.setItem(ANVIL_INPUT_LEFT, leftItem) if (player.gameMode != GameMode.CREATIVE) { - player.level -= xpCost + if(linearCost){ + player.totalExperience -= xpCost + } else{ + player.level -= xpCost + } } // Then we try to find the new values for the anvil diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index c6b3570..ad9a103 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -136,12 +136,13 @@ class PrepareAnvilListener : Listener { event.result = resultItem if (DependencyManager.tryTreatAnvilResult(event, resultItem)) return true - // Maybe add an option on custom craft to ignore/not ignore penalty ?? - var xpCost = recipe.levelCostPerCraft * amount - xpCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.CUSTOM_CRAFT) + val xpCost = recipe.determineCost(amount, first, resultItem) - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, xpCost, true) + val levelCost = + if (recipe.removeExactXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) + else AnvilXpUtil.calculateLevelForXp(xpCost) + AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, levelCost, true) return true } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt index 07db447..6633a3d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt @@ -5,6 +5,8 @@ import org.bukkit.configuration.ConfigurationSection import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant +import xyz.alexcrea.cuanvil.util.AnvilUseType +import xyz.alexcrea.cuanvil.util.AnvilXpUtil class AnvilCustomRecipe( val name: String, @@ -196,5 +198,18 @@ class AnvilCustomRecipe( return name } + fun determineCost(amount: Int, first: ItemStack, resultItem: ItemStack): Int { + // First we determine the non linear level cost + var levelCost = levelCostPerCraft * amount + // TODO Maybe add an option per custom craft to ignore/not ignore penalty ?? + levelCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.CUSTOM_CRAFT) + + var xpCost = AnvilXpUtil.calculateXpForLevel(levelCost) + // Then we add the linear cost + xpCost += XpCostPerCraft * amount + + return xpCost + } + } From 8914369d38eac44e6bf763e6fd0c9ac97e5369c9 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 4 Jul 2025 16:19:42 +0200 Subject: [PATCH 04/11] linear cost first gui iteration --- .../elements/CustomRecipeSubSettingGui.java | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java index d0f9f2e..a6715c4 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java @@ -43,6 +43,7 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { Pattern pattern = new Pattern( GuiSharedConstant.EMPTY_GUI_FULL_LINE, "01203450D", + "0ab000000", "B00000000" ); this.pane = new PatternPane(0, 0, 9, 3, pattern); @@ -51,11 +52,16 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { prepareStaticValues(); } - BoolSettingsGui.BoolSettingFactory exactCountFactory; - IntSettingsGui.IntSettingFactory xpCostFactory; - ItemSettingGui.ItemSettingFactory leftItemFactory; - ItemSettingGui.ItemSettingFactory rightItemFactory; - ItemSettingGui.ItemSettingFactory resultItemFactory; + private BoolSettingsGui.BoolSettingFactory exactCountFactory; + private BoolSettingsGui.BoolSettingFactory removeExactLinearXpFactory; + private GuiItem noRemoveExactLinearXp; + + private IntSettingsGui.IntSettingFactory levelCostFactory; + private IntSettingsGui.IntSettingFactory linearXpCostFactory; + + private ItemSettingGui.ItemSettingFactory leftItemFactory; + private ItemSettingGui.ItemSettingFactory rightItemFactory; + private ItemSettingGui.ItemSettingFactory resultItemFactory; private void prepareStaticValues() { @@ -74,19 +80,38 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { this.pane.bindItem('D', new GuiItem(deleteItem, GuiGlobalActions.openGuiAction(createDeleteGui()), CustomAnvil.instance)); // Displayed item will be updated later - IntRange costRange = AnvilCustomRecipe.Companion.getXP_COST_CONFIG_RANGE(); this.exactCountFactory = new BoolSettingsGui.BoolSettingFactory("§8Exact count ?", this, ConfigHolder.CUSTOM_RECIPE_HOLDER, this.anvilRecipe + "." + AnvilCustomRecipe.EXACT_COUNT_CONFIG, AnvilCustomRecipe.DEFAULT_EXACT_COUNT_CONFIG); - this.xpCostFactory = new IntSettingsGui.IntSettingFactory("§8Recipe Xp Cost", this, - this.anvilRecipe +"."+AnvilCustomRecipe.XP_LEVEL_COST_CONFIG, + this.removeExactLinearXpFactory = new BoolSettingsGui.BoolSettingFactory("§8Remove exact linear xp ?", this, + ConfigHolder.CUSTOM_RECIPE_HOLDER, + this.anvilRecipe + "." + AnvilCustomRecipe.REMOVE_EXACT_XP_CONFIG, AnvilCustomRecipe.DEFAULT_REMOVE_EXACT_XP_CONFIG); + + ItemStack item = new ItemStack(Material.BARRIER); + ItemMeta meta = item.getItemMeta(); + assert meta != null; + + meta.setDisplayName("§cRemove exact linear xp ?"); + meta.setLore(Collections.singletonList("§7Not usable if linear cost is 0")); + item.setItemMeta(meta); + this.noRemoveExactLinearXp = new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); + + this.levelCostFactory = new IntSettingsGui.IntSettingFactory("§8Recipe Level Cost", this, + this.anvilRecipe + "." + AnvilCustomRecipe.XP_LEVEL_COST_CONFIG, ConfigHolder.CUSTOM_RECIPE_HOLDER, null, costRange.getFirst(), costRange.getLast(), AnvilCustomRecipe.DEFAULT_XP_LEVEL_COST_CONFIG, 1, 5, 10); + this.linearXpCostFactory = new IntSettingsGui.IntSettingFactory("§8Recipe Linear Xp Cost", this, + this.anvilRecipe + "." + AnvilCustomRecipe.LINEAR_XP_COST_CONFIG, + ConfigHolder.CUSTOM_RECIPE_HOLDER, + null, + 0, Integer.MAX_VALUE, AnvilCustomRecipe.DEFAULT_LINEAR_XP_COST_CONFIG, 1, 10, 100, 1000, 10000); + + // Right part of the gui this.leftItemFactory = new ItemSettingGui.ItemSettingFactory("§eRecipe Left §8Item", this, this.anvilRecipe + "." + AnvilCustomRecipe.LEFT_ITEM_CONFIG, ConfigHolder.CUSTOM_RECIPE_HOLDER, @@ -158,8 +183,18 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { GuiItem exactCountItem = this.exactCountFactory.getItem(); this.pane.bindItem('1', exactCountItem); - GuiItem xpCostItem = this.xpCostFactory.getItem(Material.EXPERIENCE_BOTTLE); - this.pane.bindItem('2', xpCostItem); + if (anvilRecipe.getXpCostPerCraft() != 0) { + this.pane.bindItem('a', noRemoveExactLinearXp); + } else { + this.pane.bindItem('a', removeExactLinearXpFactory.getItem()); + } + + GuiItem levelCostItem = this.levelCostFactory.getItem(Material.EXPERIENCE_BOTTLE); + this.pane.bindItem('2', levelCostItem); + + + GuiItem xpCostItem = this.linearXpCostFactory.getItem(Material.EXPERIENCE_BOTTLE); + this.pane.bindItem('b', xpCostItem); GuiItem leftGuiItem = this.leftItemFactory.getItem(); this.pane.bindItem('3', leftGuiItem); @@ -169,7 +204,7 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { GuiItem resultGuiItem = this.resultItemFactory.getItem(); this.pane.bindItem('5', resultGuiItem); - + update(); } From b3cc234ef31bbd9b595cfb287837b9a6874b77ee Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 4 Jul 2025 22:52:18 +0200 Subject: [PATCH 05/11] add api for recipe changes --- .../cuanvil/api/AnvilRecipeBuilder.java | 90 +++++++++++++++++-- .../cuanvil/listener/AnvilResultListener.kt | 6 +- .../cuanvil/listener/PrepareAnvilListener.kt | 2 +- .../cuanvil/recipe/AnvilCustomRecipe.kt | 6 +- .../cuanvil/api/AnvilRecipeBuilderTest.java | 6 +- .../api/CustomAnvilRecipeApiTests.java | 4 +- 6 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java b/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java index 9d33bfb..76b4a68 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java @@ -14,7 +14,10 @@ public class AnvilRecipeBuilder { private @NotNull String name; private boolean exactCount; - private int xpCostPerCraft; + private int levelCostPerCraft; + private int linearXpCostPerCraft; + + private boolean removeExactLinearXp; private @Nullable ItemStack leftItem; private @Nullable ItemStack rightItem; @@ -31,7 +34,7 @@ public class AnvilRecipeBuilder { this.name = name; this.exactCount = true; - this.xpCostPerCraft = 1; + this.levelCostPerCraft = 1; this.leftItem = null; this.rightItem = null; @@ -89,9 +92,11 @@ public class AnvilRecipeBuilder { * Get the xp level cost per craft. * * @return The xp level cost per craft + * @deprecated use {@link #getLevelCostPerCraft() getLevelCostPerCraft} instead */ + @Deprecated(since = "1.13.0") public int getXpCostPerCraft() { - return xpCostPerCraft; + return getLevelCostPerCraft(); } /** @@ -99,9 +104,78 @@ public class AnvilRecipeBuilder { * * @param xpCostPerCraft The xp level cost per craft * @return This recipe builder instance. + * @deprecated use {@link #setLevelCostPerCraft(int) setLevelCostPerCraft} instead */ + @Deprecated(since = "1.13.0") public AnvilRecipeBuilder setXpCostPerCraft(int xpCostPerCraft) { - this.xpCostPerCraft = xpCostPerCraft; + return setLevelCostPerCraft(xpCostPerCraft); + } + + /** + * Get the xp level cost per craft. + * + * @return The xp level cost per craft + */ + public int getLevelCostPerCraft() { + return levelCostPerCraft; + } + + /** + * Sets the xp level cost per craft. + * + * @param levelCostPerCraft The xp level cost per craft + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setLevelCostPerCraft(int levelCostPerCraft) { + this.levelCostPerCraft = levelCostPerCraft; + return this; + } + + /** + * Get the linear xp cost (not xp level cost) per craft. + * + * @return The xp level cost per craft + */ + public int getLinearXpCostPerCraft() { + return linearXpCostPerCraft; + } + + /** + * Sets the linear xp cost (not xp level cost) per craft. + * + * @param linearXpCostPerCraft The linear xp cost per craft + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setLinearXpCostPerCraft(int linearXpCostPerCraft) { + this.linearXpCostPerCraft = linearXpCostPerCraft; + return this; + } + + /** + * Get if the linear xp should get removed by an exact amount. + *

+ * If false (default) level cost will be the level that would be reached by a player with this amount of xp. + * If true will require the level that has at least the specified level of xp then on click remove only the necessary xp + *

+ * linear xp cost are applied after level cost + * @return if we should remove the exact amount of linear xp + */ + public boolean isRemoveExactLinearXp() { + return removeExactLinearXp; + } + + /** + * Set if the linear xp should get removed by an exact amount. + *

+ * If false (default) level cost will be the level that would be reached by a player with this amount of xp. + * If true will require the level that has at least the specified level of xp then on click remove only the necessary xp + *

+ * linear xp cost are applied after level cost + * @param removeExactLinearXp if we should remove the exact amount of linear xp + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setRemoveExactLinearXp(boolean removeExactLinearXp) { + this.removeExactLinearXp = removeExactLinearXp; return this; } @@ -182,12 +256,14 @@ public class AnvilRecipeBuilder { */ @Nullable // null if missing argument public AnvilCustomRecipe build() { - if(leftItem == null || resultItem == null) return null; + if (leftItem == null || resultItem == null) return null; return new AnvilCustomRecipe( this.name, this.exactCount, - this.xpCostPerCraft, + this.levelCostPerCraft, + this.linearXpCostPerCraft, + this.removeExactLinearXp, this.leftItem, this.rightItem, this.resultItem ); } @@ -198,7 +274,7 @@ public class AnvilRecipeBuilder { * * @return True if successful. */ - public boolean registerIfAbsent(){ + public boolean registerIfAbsent() { return CustomAnvilRecipeApi.addRecipe(this); } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 8e37169..74c62c0 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -132,12 +132,12 @@ class AnvilResultListener : Listener { val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) val xpCost = recipe.determineCost(amount, leftItem, output) val finalCost = - if (recipe.removeExactXp) xpCost + if (recipe.removeExactLinearXp) xpCost else AnvilXpUtil.calculateLevelForXp(xpCost) CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $finalCost, level: ${player.level}, result: ${player.totalExperience < finalCost} ${player.level < finalCost}") if (player.gameMode != GameMode.CREATIVE){ - if(recipe.removeExactXp){ + if(recipe.removeExactLinearXp){ if(player.totalExperience < finalCost) return }else if(player.level < finalCost) return } @@ -149,7 +149,7 @@ class AnvilResultListener : Listener { // Handle not creative middle click... if (event.click != ClickType.MIDDLE && - !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, finalCost, recipe.removeExactXp) + !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, finalCost, recipe.removeExactLinearXp) ) return // Finally, we add the item to the player diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index ad9a103..604c20f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -139,7 +139,7 @@ class PrepareAnvilListener : Listener { val xpCost = recipe.determineCost(amount, first, resultItem) val levelCost = - if (recipe.removeExactXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) + if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) else AnvilXpUtil.calculateLevelForXp(xpCost) AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, levelCost, true) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt index 6633a3d..fa1a977 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt @@ -17,7 +17,7 @@ class AnvilCustomRecipe( var levelCostPerCraft: Int, var XpCostPerCraft: Int, - var removeExactXp: Boolean, + var removeExactLinearXp: Boolean, var leftItem: ItemStack?, var rightItem: ItemStack?, @@ -96,7 +96,7 @@ class AnvilCustomRecipe( fileConfig["$name.$XP_LEVEL_COST_CONFIG"] = levelCostPerCraft fileConfig["$name.$LINEAR_XP_COST_CONFIG"] = XpCostPerCraft - fileConfig["$name.$REMOVE_EXACT_XP_CONFIG"] = removeExactXp + fileConfig["$name.$REMOVE_EXACT_XP_CONFIG"] = removeExactLinearXp fileConfig["$name.$LEFT_ITEM_CONFIG"] = leftItem fileConfig["$name.$RIGHT_ITEM_CONFIG"] = rightItem @@ -132,7 +132,7 @@ class AnvilCustomRecipe( DEFAULT_LINEAR_XP_COST_CONFIG ) - this.removeExactXp = ConfigHolder.CUSTOM_RECIPE_HOLDER.config.getBoolean( + this.removeExactLinearXp = ConfigHolder.CUSTOM_RECIPE_HOLDER.config.getBoolean( "$name.$REMOVE_EXACT_XP_CONFIG", DEFAULT_REMOVE_EXACT_XP_CONFIG ) diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java b/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java index 2b57bda..9a65e12 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java @@ -63,9 +63,9 @@ public class AnvilRecipeBuilderTest extends SharedOnlyMockBukkit { @Test void setXpCostPerCraft(){ - assertEquals(1, builder.getXpCostPerCraft()); - builder.setXpCostPerCraft(2); - assertEquals(2, builder.getXpCostPerCraft()); + assertEquals(1, builder.getLevelCostPerCraft()); + builder.setLevelCostPerCraft(2); + assertEquals(2, builder.getLevelCostPerCraft()); } @Test diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java index 3ebdd7c..c93b7e7 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java @@ -61,7 +61,7 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { // Add and test recipe AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); - builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setXpCostPerCraft(2); + builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setLevelCostPerCraft(2); assertTrue(builder.registerIfAbsent()); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); @@ -125,7 +125,7 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { builder.setExactCount(false) .setLeftItem(stick) .setResultItem(stick2) - .setXpCostPerCraft(2); + .setLevelCostPerCraft(2); assertTrue(builder.registerIfAbsent()); From 87ec40a7ab3dfab00680fd89c6f6883d69a66a08 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 4 Jul 2025 22:57:46 +0200 Subject: [PATCH 06/11] handle recipe list gui creation and lore --- .../config/global/CustomRecipeConfigGui.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java index ee8895c..e21ad75 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java @@ -14,7 +14,7 @@ import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe; import xyz.alexcrea.cuanvil.util.CasedStringUtil; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; public class CustomRecipeConfigGui extends MappedGuiListConfigGui getRecipeLore(AnvilCustomRecipe recipe) { + boolean shouldWork = recipe.validate(); + + ArrayList lore = new ArrayList<>(); + lore.add("§7Is valid: §" + (shouldWork ? "aYes" : "cNo")); + lore.add("§7Exact count: §" + (recipe.getExactCount() ? "aYes" : "cNo")); + lore.add("§7Recipe Level Cost: §e" + recipe.getLevelCostPerCraft()); + lore.add("§7Recipe Linear Xp Cost: §e" + recipe.getXpCostPerCraft()); + if (recipe.getXpCostPerCraft() != 0) { + lore.add("§7Exact Linear xp remove: §" + (recipe.getRemoveExactLinearXp() ? "aYes" : "cNo")); + } + return lore; + } + @Override protected LazyElement newInstanceOfGui(AnvilCustomRecipe generic, GuiItem item) { return new LazyElement<>(item, () -> new CustomRecipeSubSettingGui(this, generic)); @@ -87,7 +94,11 @@ public class CustomRecipeConfigGui extends MappedGuiListConfigGui Date: Fri, 4 Jul 2025 23:13:14 +0200 Subject: [PATCH 07/11] fix gui logic issue --- .../gui/config/list/elements/CustomRecipeSubSettingGui.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java index a6715c4..9db6d62 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java @@ -36,7 +36,7 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { public CustomRecipeSubSettingGui( @NotNull CustomRecipeConfigGui parent, @NotNull AnvilCustomRecipe anvilRecipe) { - super(3, "§e" + CasedStringUtil.snakeToUpperSpacedCase(anvilRecipe.toString()) + " §8Config"); + super(4, "§e" + CasedStringUtil.snakeToUpperSpacedCase(anvilRecipe.toString()) + " §8Config"); this.parent = parent; this.anvilRecipe = anvilRecipe; @@ -46,7 +46,7 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { "0ab000000", "B00000000" ); - this.pane = new PatternPane(0, 0, 9, 3, pattern); + this.pane = new PatternPane(0, 0, 9, 4, pattern); addPane(this.pane); prepareStaticValues(); @@ -183,7 +183,7 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { GuiItem exactCountItem = this.exactCountFactory.getItem(); this.pane.bindItem('1', exactCountItem); - if (anvilRecipe.getXpCostPerCraft() != 0) { + if (anvilRecipe.getXpCostPerCraft() == 0) { this.pane.bindItem('a', noRemoveExactLinearXp); } else { this.pane.bindItem('a', removeExactLinearXpFactory.getItem()); From b6f5c7b7a148344163b9d3a295d6ae0411adb370 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 5 Jul 2025 04:03:11 +0200 Subject: [PATCH 08/11] linear xp cost pre anvil unit test --- .../api/CustomAnvilRecipeApiTests.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java index c93b7e7..5e9e716 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java @@ -134,6 +134,92 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); } + @Test + public void testLinearXpCost() { + String recipeName = "stick_recipe"; + ItemStack stick = new ItemStackMock(Material.STICK); + ItemStack stick2 = new ItemStackMock(Material.STICK, 2); + ItemStack stick5 = new ItemStackMock(Material.STICK, 5); + ItemStack stick10 = new ItemStackMock(Material.STICK, 10); + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + stick, stick, + null + ); + + AnvilFuseTestData legalResultData1 = new AnvilFuseTestData( + stick, stick, + null, stick2, null, + 1, + null, null + ); + + AnvilFuseTestData legalResultData2 = new AnvilFuseTestData( + stick5, stick, + null, stick10, null, + 4, + null, null + ); + + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + + AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); + builder.setExactCount(false) + .setLeftItem(stick) + .setResultItem(stick2) + .setLevelCostPerCraft(0) + .setLinearXpCostPerCraft(10); + + assertTrue(builder.registerIfAbsent()); + + // Now working test + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData1); + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + } + + @Test + public void testLinearXpCostRemoveExact() { + String recipeName = "stick_recipe"; + ItemStack stick = new ItemStackMock(Material.STICK); + ItemStack stick2 = new ItemStackMock(Material.STICK, 2); + ItemStack stick5 = new ItemStackMock(Material.STICK, 5); + ItemStack stick10 = new ItemStackMock(Material.STICK, 10); + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + stick, stick, + null + ); + + AnvilFuseTestData legalResultData1 = new AnvilFuseTestData( + stick, stick, + null, stick2, null, + 2, + null, null + ); + + AnvilFuseTestData legalResultData2 = new AnvilFuseTestData( + stick5, stick, + null, stick10, null, + 5, + null, null + ); + + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + + AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); + builder.setExactCount(false) + .setLeftItem(stick) + .setResultItem(stick2) + .setLinearXpCostPerCraft(10) + .setRemoveExactLinearXp(true); + + assertTrue(builder.registerIfAbsent()); + + // Now working test + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData1); + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + } + @Nullable public static AnvilCustomRecipe getByName(String name){ for (AnvilCustomRecipe registeredRecipe : CustomAnvilRecipeApi.getRegisteredRecipes()) { From 6078136a807b388fbf4ae2ff40a12bae9f534340 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 5 Jul 2025 04:05:06 +0200 Subject: [PATCH 09/11] change api recipe default cost also update api recipe builder unit test --- .../cuanvil/api/AnvilRecipeBuilder.java | 12 +++--- .../cuanvil/api/AnvilRecipeBuilderTest.java | 40 ++++++++++++++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java b/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java index 76b4a68..4292fa0 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java @@ -26,7 +26,7 @@ public class AnvilRecipeBuilder { /** * Instantiates a new Anvil recipe builder. * exact count default to true. - * xp cost per craft default to 1. + * xp level and linear cost per craft default to 0. * * @param name The recipe name */ @@ -34,7 +34,9 @@ public class AnvilRecipeBuilder { this.name = name; this.exactCount = true; - this.levelCostPerCraft = 1; + this.levelCostPerCraft = 0; + this.linearXpCostPerCraft = 0; + this.removeExactLinearXp = false; this.leftItem = null; this.rightItem = null; @@ -63,7 +65,7 @@ public class AnvilRecipeBuilder { } /** - * Get if the recipe is exact count. + * Get if the recipe is exact count. (default 0) *

* Exact count mean the recipe can only be crafted 1 by 1. * If set to false, then it will craft as much as possible in 1 go and will keep unused material onto the anvil inventory. @@ -89,7 +91,7 @@ public class AnvilRecipeBuilder { } /** - * Get the xp level cost per craft. + * Get the xp level cost per craft. (default 0) * * @return The xp level cost per craft * @deprecated use {@link #getLevelCostPerCraft() getLevelCostPerCraft} instead @@ -112,7 +114,7 @@ public class AnvilRecipeBuilder { } /** - * Get the xp level cost per craft. + * Get the xp level cost per craft. (default 0) * * @return The xp level cost per craft */ diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java b/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java index 9a65e12..d9ff329 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java @@ -12,10 +12,15 @@ import static org.junit.jupiter.api.Assertions.*; public class AnvilRecipeBuilderTest extends SharedOnlyMockBukkit { private AnvilRecipeBuilder builder; + private AnvilRecipeBuilder builder2; @BeforeEach public void setup() { builder = new AnvilRecipeBuilder("test"); + builder2 = new AnvilRecipeBuilder("test"); + + builder2.setLeftItem(new ItemStack(Material.STICK)); + builder2.setResultItem(new ItemStack(Material.STICK)); } @Test @@ -38,6 +43,7 @@ public class AnvilRecipeBuilderTest extends SharedOnlyMockBukkit { .setResultItem(new ItemStack(Material.STICK)); assertNotNull(builder.build()); + assertNotNull(builder2.build()); } @Test @@ -63,23 +69,39 @@ public class AnvilRecipeBuilderTest extends SharedOnlyMockBukkit { @Test void setXpCostPerCraft(){ - assertEquals(1, builder.getLevelCostPerCraft()); - builder.setLevelCostPerCraft(2); - assertEquals(2, builder.getLevelCostPerCraft()); + assertEquals(0, builder2.getLevelCostPerCraft()); + assertEquals(0, builder2.build().getLevelCostPerCraft()); + builder2.setLevelCostPerCraft(2); + assertEquals(2, builder2.getLevelCostPerCraft()); + assertEquals(2, builder2.build().getLevelCostPerCraft()); } + @Test + void setLinearXpCostPerCraft(){ + assertEquals(0, builder2.getLinearXpCostPerCraft()); + assertEquals(0, builder2.build().getXpCostPerCraft()); + builder2.setLinearXpCostPerCraft(2); + assertEquals(2, builder2.getLinearXpCostPerCraft()); + assertEquals(2, builder2.build().getXpCostPerCraft()); + } + + @Test void setExactCount(){ - assertTrue(builder.isExactCount()); - builder.setExactCount(false); - assertFalse(builder.isExactCount()); + assertTrue(builder2.isExactCount()); + assertTrue(builder2.build().getExactCount()); + builder2.setExactCount(false); + assertFalse(builder2.isExactCount()); + assertFalse(builder2.build().getExactCount()); } @Test void setName(){ - assertEquals("test", builder.getName()); - builder.setName("other"); - assertEquals("other", builder.getName()); + assertEquals("test", builder2.getName()); + assertEquals("test", builder2.build().getName()); + builder2.setName("other"); + assertEquals("other", builder2.getName()); + assertEquals("other", builder2.build().getName()); } } From a0fa1e3fe25bfe3dd9f32b9d75f486f4b477af0a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 5 Jul 2025 22:41:10 +0200 Subject: [PATCH 10/11] manually set level and exp % --- .../cuanvil/listener/AnvilResultListener.kt | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 74c62c0..0e9141f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -136,10 +136,13 @@ class AnvilResultListener : Listener { else AnvilXpUtil.calculateLevelForXp(xpCost) CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $finalCost, level: ${player.level}, result: ${player.totalExperience < finalCost} ${player.level < finalCost}") - if (player.gameMode != GameMode.CREATIVE){ - if(recipe.removeExactLinearXp){ - if(player.totalExperience < finalCost) return - }else if(player.level < finalCost) return + if (player.gameMode != GameMode.CREATIVE) { + if (recipe.removeExactLinearXp) { + val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) + val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp + val totalXp = levelXp + player.exp * delta + if (totalXp < finalCost) return + } else if (player.level < finalCost) return } // We give the item manually @@ -149,7 +152,17 @@ class AnvilResultListener : Listener { // Handle not creative middle click... if (event.click != ClickType.MIDDLE && - !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, finalCost, recipe.removeExactLinearXp) + !handleCustomCraftClick( + event, + recipe, + inventory, + player, + leftItem, + rightItem, + amount, + finalCost, + recipe.removeExactLinearXp + ) ) return // Finally, we add the item to the player @@ -178,9 +191,21 @@ class AnvilResultListener : Listener { inventory.setItem(ANVIL_INPUT_LEFT, leftItem) if (player.gameMode != GameMode.CREATIVE) { - if(linearCost){ - player.totalExperience -= xpCost - } else{ + if (linearCost) { + val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) + val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp + var totalXp = levelXp + player.exp * delta + totalXp -= xpCost + + val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt()) + + val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel) + val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp + val xp = (totalXp - newLevelXp) / newDelta + + player.level = newLevel + player.exp = xp / newDelta + } else { player.level -= xpCost } } From e93c2cc121d4ab0c3764664cb49d7a2972f2f58f Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 5 Jul 2025 23:04:42 +0200 Subject: [PATCH 11/11] add unit test for new features --- .../delilaheve/util/EnchantmentUtilTests.java | 22 +++--- .../cuanvil/anvil/AnvilFuseTests.java | 10 +-- .../cuanvil/api/ConflictApiTests.java | 8 +- .../api/CustomAnvilRecipeApiTests.java | 75 ++++++++++++------- .../cuanvil/api/UnitRepairApiTests.java | 8 +- .../cuanvil/data/AnvilClickTestData.java | 7 ++ .../cuanvil/data/AnvilFuseTestData.java | 7 ++ .../cuanvil/data/TestDataContainer.java | 8 +- .../cuanvil/util/AnvilFuseTestUtil.java | 66 ++++++++++++---- 9 files changed, 145 insertions(+), 66 deletions(-) diff --git a/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java b/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java index 48efc6b..10ea252 100644 --- a/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java +++ b/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java @@ -88,16 +88,16 @@ public class EnchantmentUtilTests extends ConfigResetCustomAnvilTest { ); // Test with no permission - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData2); + nullResultData.executeTest(anvil, player); + nullResultData2.executeTest(anvil, player); // Add permission PermissionAttachment attachment = player.addAttachment(plugin); attachment.setPermission(permission, true); // Test with new permission - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + legalResultData.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); } @Test @@ -161,24 +161,24 @@ public class EnchantmentUtilTests extends ConfigResetCustomAnvilTest { ); // Test failing result first - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData2); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData3); + nullResultData2.executeTest(anvil, player); + nullResultData3.executeTest(anvil, player); // Test working sharpness 2 - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); // Set merge limit to 2 & test ConfigHolder.DEFAULT_CONFIG.getConfig().set("disable-merge-over.minecraft:sharpness", 1); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Add permission PermissionAttachment attachment = player.addAttachment(plugin); attachment.setPermission(permission, true); // Test working sharpness 2 - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData3); + legalResultData.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); + legalResultData3.executeTest(anvil, player); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java b/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java index 60df37e..19f1d6f 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java @@ -67,7 +67,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { 5 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } @Test @@ -87,7 +87,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { 5 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } @Test @@ -101,7 +101,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { null ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } // Note: currently anvil can only have null name. maybe handle differently later @@ -117,10 +117,10 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { AnvilFuseTestData data = new AnvilFuseTestData( base, null, expected, expected, null, - 1, 1, null + 1, null, 1 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java index b1d7564..1a9a256 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java @@ -71,7 +71,7 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest { Assertions.assertNotNull(sharpness); // Testing default conflict (illegal item should not be produced) - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Try to find & remove conflict EnchantConflictGroup conflict = findGroup("sword_enchant_conflict"); @@ -79,7 +79,7 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest { // Test what happen when we remove the conflict (illegal item should be allowed) ConflictAPI.removeConflict(conflict); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); // We create and add a new conflict ConflictBuilder builder = new ConflictBuilder("sword_enchant_conflict"); @@ -88,11 +88,11 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest { // Nothing should change as it is not new: it was previously deleted Assertions.assertFalse(builder.registerIfNew()); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); // Now the conflict should be registered and conflict should exist Assertions.assertTrue(builder.registerIfAbsent()); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); } @Test diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java index 5e9e716..651b873 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.api; import org.bukkit.Material; +import org.bukkit.event.Event; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.Inventory; @@ -11,10 +12,11 @@ import org.junit.jupiter.api.Test; import org.mockbukkit.mockbukkit.entity.PlayerMock; import org.mockbukkit.mockbukkit.inventory.ItemStackMock; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.data.AnvilClickTestData; +import xyz.alexcrea.cuanvil.data.TestDataContainer; import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe; import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; -import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; import static org.junit.jupiter.api.Assertions.*; @@ -57,14 +59,14 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { ); // Testing default conflict (no recipe exist) - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Add and test recipe AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setLevelCostPerCraft(2); assertTrue(builder.registerIfAbsent()); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); AnvilCustomRecipe recipe = getByName(recipeName); assertNotNull(recipe); @@ -72,21 +74,21 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { // Remove recipe assertTrue(CustomAnvilRecipeApi.removeRecipe(recipe)); assertFalse(CustomAnvilRecipeApi.removeRecipe(recipe)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); recipe = getByName(recipeName); assertNull(recipe); // Try to add deleted recipe with no override (should not add) assertFalse(CustomAnvilRecipeApi.addRecipe(builder, false)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); recipe = getByName(recipeName); assertNull(recipe); // Try to add deleted recipe with override (should add) assertTrue(CustomAnvilRecipeApi.addRecipe(builder, true)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); recipe = getByName(recipeName); assertNotNull(recipe); @@ -119,7 +121,7 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { null, null ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); builder.setExactCount(false) @@ -130,8 +132,8 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { assertTrue(builder.registerIfAbsent()); // Now working test - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData1); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + legalResultData1.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); } @Test @@ -142,26 +144,37 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { ItemStack stick5 = new ItemStackMock(Material.STICK, 5); ItemStack stick10 = new ItemStackMock(Material.STICK, 10); + AnvilFuseTestData nullResultData = new AnvilFuseTestData( stick, stick, null ); - AnvilFuseTestData legalResultData1 = new AnvilFuseTestData( + TestDataContainer legalResultData1 = new TestDataContainer(new AnvilFuseTestData( stick, stick, null, stick2, null, 1, null, null - ); + ), new AnvilClickTestData( + null, null, null, stick2, + 1, + Event.Result.DENY, true, Event.Result.DENY - AnvilFuseTestData legalResultData2 = new AnvilFuseTestData( + )); + + TestDataContainer legalResultData2 = new TestDataContainer(new AnvilFuseTestData( stick5, stick, null, stick10, null, 4, null, null - ); + ), new AnvilClickTestData( + null, null, null, stick10, + 4, + Event.Result.DENY, true, Event.Result.DENY - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + )); + + nullResultData.executeTest(anvil, player); AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); builder.setExactCount(false) @@ -173,8 +186,8 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { assertTrue(builder.registerIfAbsent()); // Now working test - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData1); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + legalResultData1.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); } @Test @@ -190,21 +203,30 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { null ); - AnvilFuseTestData legalResultData1 = new AnvilFuseTestData( + TestDataContainer legalResultData1 = new TestDataContainer(new AnvilFuseTestData( stick, stick, null, stick2, null, 2, null, null - ); + ), new AnvilClickTestData( + null, null, null, stick2, + 2, + Event.Result.DENY, true, Event.Result.DENY - AnvilFuseTestData legalResultData2 = new AnvilFuseTestData( + )); + + TestDataContainer legalResultData2 = new TestDataContainer(new AnvilFuseTestData( stick5, stick, null, stick10, null, 5, null, null - ); + ), new AnvilClickTestData( + null, null, null, stick10, + 5, + Event.Result.DENY, true, Event.Result.DENY + )); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); builder.setExactCount(false) @@ -216,14 +238,17 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { assertTrue(builder.registerIfAbsent()); // Now working test - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData1); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + legalResultData1.executeTest(anvil, player); + //TODO check exp ? + System.out.printf(String.valueOf(player.getExp())); + legalResultData2.executeTest(anvil, player); + //TODO check exp ? } @Nullable - public static AnvilCustomRecipe getByName(String name){ + public static AnvilCustomRecipe getByName(String name) { for (AnvilCustomRecipe registeredRecipe : CustomAnvilRecipeApi.getRegisteredRecipes()) { - if(registeredRecipe.getName().contentEquals(name)){ + if (registeredRecipe.getName().contentEquals(name)) { return registeredRecipe; } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java index 8166797..adda4e1 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java @@ -58,7 +58,7 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest { 2 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); } @Test @@ -76,7 +76,7 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest { // Remove unit repair assertTrue(UnitRepairApi.removeUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // see override assertFalse(UnitRepairApi.addUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE, 0.25)); @@ -107,12 +107,12 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest { 2 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Add unit repair assertTrue(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); assertFalse(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java index 65d7ddb..002b194 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java +++ b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java @@ -1,8 +1,11 @@ package xyz.alexcrea.cuanvil.data; +import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; public record AnvilClickTestData( @Nullable ItemStack leftItem, @@ -47,4 +50,8 @@ public record AnvilClickTestData( int levelCost) { this(expectedCursor, levelCost, null); } + + public void executeTest(AnvilInventory anvil, Player player){ + AnvilFuseTestUtil.executeAnvilClickTest(anvil, player, this); + } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java index a01d0ab..8542d41 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java +++ b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java @@ -1,7 +1,10 @@ package xyz.alexcrea.cuanvil.data; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; public record AnvilFuseTestData( @Nullable ItemStack leftItem, @@ -51,4 +54,8 @@ public record AnvilFuseTestData( ); } + public void executeTest(AnvilInventory anvil, HumanEntity player){ + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, this); + } + } diff --git a/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java b/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java index 2c8ff93..b4f47a5 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java +++ b/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java @@ -16,17 +16,17 @@ public record TestDataContainer( ) { public void executeTest(AnvilInventory anvil, Player player) { - executeFuseTest(anvil, player); - if (clickData != null) executeClickTest(anvil, player); + fuseData.executeTest(anvil, player); + if (clickData != null) clickData.executeTest(anvil, player); } public void executeFuseTest(AnvilInventory anvil, HumanEntity player) { - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, fuseData); + fuseData.executeTest(anvil, player); } public void executeClickTest(AnvilInventory anvil, Player player) { Assertions.assertNotNull(clickData); - AnvilFuseTestUtil.executeAnvilClickTest(anvil, player, clickData); + clickData.executeTest(anvil, player); } public @NotNull TestDataContainer nullifyResult() { diff --git a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java index b73b884..1d3c5f4 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java +++ b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java @@ -110,22 +110,60 @@ public class AnvilFuseTestUtil { Assertions.assertEquals(player.getOpenInventory().getTopInventory(), anvil, "Openned inventory is not anvil"); + ItemStack afterLeft = data.expectedAfterLeftPlaced(); + ItemStack afterRight = data.expectedAfterRightPlaced(); + ItemStack afterBoth = data.expectedResult(); + // Fist, test null result(s) + // Test with only the left item - anvil.setItem(1, null); // We clear the right slot in case something was there - testPlacingItem(anvil, player, - 0, data.expectedPriceAfterLeftPlaced(), - data.leftItem(), data.expectedAfterLeftPlaced()); + if(afterLeft == null){ + anvil.setItem(1, null); // We clear the right slot in case something was there + testPlacingItem(anvil, player, + 0, data.expectedPriceAfterLeftPlaced(), + data.leftItem(), null); + } // Test with only the right item - anvil.setItem(0, null); // We only want the right item. so we remove the left one - testPlacingItem(anvil, player, - 1, data.expectedPriceAfterRightPlaced(), - data.rightItem(), data.expectedAfterRightPlaced()); + if(afterRight == null){ + anvil.setItem(0, null); // We only want the right item. so we remove the left one + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterRightPlaced(), + data.rightItem(), null); + } // Test with both placed - testPlacingItem(anvil, player, - 0, data.expectedPriceAfterBothPlaced(), - data.leftItem(), data.expectedResult()); + if(afterBoth == null){ + anvil.setItem(0, data.leftItem()); + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterBothPlaced(), + data.rightItem(), data.expectedResult()); + } + + // Then, test non null result(s) + + // Test with only the left item + if(afterLeft != null){ + anvil.setItem(1, null); // We clear the right slot in case something was there + testPlacingItem(anvil, player, + 0, data.expectedPriceAfterLeftPlaced(), + data.leftItem(), afterLeft); + } + + // Test with only the right item + if(afterRight != null){ + anvil.setItem(0, null); // We only want the right item. so we remove the left one + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterRightPlaced(), + data.rightItem(), afterRight); + } + + // Test with both placed + if(afterBoth != null){ + anvil.setItem(0, data.leftItem()); + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterBothPlaced(), + data.rightItem(), afterBoth); + } } public static void executeAnvilClickTest( @@ -139,6 +177,7 @@ public class AnvilFuseTestUtil { ItemStack result = anvil.getResult(); player.setLevel(0); + player.setExp(0); player.setItemOnCursor(null); // Do a test with not enough level @@ -151,6 +190,7 @@ public class AnvilFuseTestUtil { assertEqual(null, player.getItemOnCursor()); } player.setLevel(data.levelCost()); + player.setExp(0); player.setItemOnCursor(null); simulateClick(anvil, player, data.expectedResult()); @@ -208,7 +248,7 @@ public class AnvilFuseTestUtil { public static void assertEqual(@Nullable ItemStack expected, @Nullable ItemStack other) { boolean secondIsAir = isAir(other); if (isAir(expected)) - Assertions.assertTrue(secondIsAir, "Item " + other + " was not air but was expected to be"); + Assertions.assertTrue(secondIsAir, "Item " + other + " was not air but was expected to be."); else { Assertions.assertFalse(secondIsAir, "Item " + other + " is air but was expected to be " + expected); @@ -225,7 +265,7 @@ public class AnvilFuseTestUtil { public static void assertPriceEqual(Integer expectedPrice, int price) { if (expectedPrice == null) return; - Assertions.assertEquals(expectedPrice, price); + Assertions.assertEquals(expectedPrice, price, "Price of anvil fuse was wrong"); } }