From a6eff2d6d0c8e06b5847b8cec8c98a330588bb84 Mon Sep 17 00:00:00 2001 From: DrMagicalStone <45792652+DrMagicalStone@users.noreply.github.com> Date: Sun, 25 Sep 2022 03:18:35 +0800 Subject: [PATCH 1/2] (fix) add exp and subtract exp works consistently Now set a player's exp point to the same number by the method "addPlayerExpPoints" will always make the player's exp point the same as "subtractExpPoints". --- .../cat/nyaa/ukit/utils/ExperienceUtils.java | 79 +++++++++++++------ 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java b/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java index cccc635..d66a81b 100644 --- a/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java +++ b/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java @@ -1,6 +1,6 @@ package cat.nyaa.ukit.utils; -import com.google.common.primitives.Ints; +import com.google.common.base.Strings; import org.bukkit.entity.Player; public final class ExperienceUtils { @@ -10,12 +10,32 @@ public final class ExperienceUtils { /** * How much exp points at least needed to reach this level. * i.e. getLevel() = level && getExp() == 0 + * The formula is from the way how exp level grows in vanilla, and the formula is: + * \[ + * f(x) = + * \left \{ + * \begin{array}{l} + * x ^ 2 + 6 x, 0 \leq x \leq 16 \\ + * \frac{5}{2} x ^ 2 - \frac{81}{2} x + 360, 16 < x \leq 31 \\ + * \frac{9}{2} x ^ 2 - \frac{325}{2} x + 2220, 31 < x \leq 21863 + * \end{array} + * \right . + * \] */ public static int getExpForLevel(int level) { - if (level < 0) throw new IllegalArgumentException(); - else if (level <= 16) return (level + 6) * level; - else if (level < 32) return Ints.checkedCast(Math.round(2.5 * level * level - 40.5 * level + 360)); - else return Ints.checkedCast(Math.round(4.5 * level * level - 162.5 * level + 2220)); + if (level < 0) { + throw new IllegalArgumentException(); + } + if (level <= 16) { + return (level + 6) * level; + } + if (level <= 31) { + return (((5 * level - 81) * level) / 2) + 360; + } + if(level <= 21863) { + return ((9 * level - 325) * level + 4440) >>> 1; + } + throw new IllegalArgumentException(Strings.lenientFormat("Out of range: The exp points of the level %s is out of the range of Integer.", level)); } /** @@ -27,27 +47,34 @@ public static int getExpPoints(Player p) { } public static void subtractExpPoints(Player p, int points) { - if (points < 0) throw new IllegalArgumentException(); - if (points == 0) return; - int total = getExpPoints(p); - if (total < points) throw new IllegalArgumentException("Negative Exp Left"); - int newLevel = getLevelForExp(total - points); - int remPoint = total - points - getExpForLevel(newLevel); - p.setLevel(newLevel); - p.setExp(0); - p.giveExp(remPoint); + addPlayerExperience(p, -points); } /** * Which level the player at if he/she has this mount of exp points - * TODO optimization + * The formula of this method is the exact inverse function of the function getLeastExpForLevel. + * \[ + * f(y) = + * \left \{ + * \begin{array}{l} + * \sqrt{9 + y} - 3 , 0 \leq y \leq 352 \\ + * \frac{\sqrt{40 y - 7839} }{10} + \frac{81}{10} , 352 < y \leq 1507 \\ + * \frac{\sqrt{72 y - 54215} }{18} + \frac{325}{18} , 1507 < y \leq 2 ^ {31} + * \end{array} + * \right . + * \] */ public static int getLevelForExp(int exp) { if (exp < 0) throw new IllegalArgumentException(); - for (int lv = 1; lv < 21000; lv++) { - if (getExpForLevel(lv) > exp) return lv - 1; + if(exp <= 352) { + return (int) Math.sqrt(9 + exp) - 3; + } + + if(exp <= 1507) { + return (int) (Math.sqrt(40 * exp - 7839) / 10 + 81d / 10d); + } else { + return (int) (Math.sqrt(72L * exp - 54215) / 18 + 325d / 18d); } - throw new IllegalArgumentException("exp too large"); } /** @@ -55,15 +82,17 @@ public static int getLevelForExp(int exp) { * Related events may be triggered. * * @param p the target player - * @param exp amount of xp to be added to the player, + * @param points amount of xp to be added to the player, * if negative, then subtract from the player. * @throws IllegalArgumentException if the player ended with negative xp */ - public static void addPlayerExperience(Player p, int exp) { - if (exp > 0) { - p.giveExp(exp); - } else if (exp < 0) { - subtractExpPoints(p, -exp); - } + public static void addPlayerExperience(Player p, int points) { + int playerPreviousExoPoints = getExpPoints(p); + if (playerPreviousExoPoints < -points) throw new IllegalArgumentException("Negative Exp Left"); + int newLevel = getLevelForExp(playerPreviousExoPoints + points); + int remPoint = playerPreviousExoPoints + points - getExpForLevel(newLevel); + p.setLevel(newLevel); + p.setExp(0); + p.giveExp(remPoint); } } \ No newline at end of file From 2c45d8b3b3291f91f032d976a489170e014301e3 Mon Sep 17 00:00:00 2001 From: DrMagicalStone <45792652+DrMagicalStone@users.noreply.github.com> Date: Sun, 25 Sep 2022 03:19:28 +0800 Subject: [PATCH 2/2] (enhancement) command: "xp supply" Command "u xp supply" moves exp points between players and their exp bottles to make the players' the player's xp point the minimum not less than the xp point or level the players' input. --- .../cat/nyaa/ukit/utils/ExperienceUtils.java | 12 +- .../nyaa/ukit/xpstore/XpStoreFunction.java | 168 +++++++++++++----- 2 files changed, 127 insertions(+), 53 deletions(-) diff --git a/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java b/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java index d66a81b..91e18c1 100644 --- a/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java +++ b/src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java @@ -22,7 +22,7 @@ public final class ExperienceUtils { * \right . * \] */ - public static int getExpForLevel(int level) { + public static int getLeastExpForLevel(int level) { if (level < 0) { throw new IllegalArgumentException(); } @@ -43,11 +43,11 @@ public static int getExpForLevel(int level) { */ public static int getExpPoints(Player p) { int pointForCurrentLevel = Math.round(p.getExpToLevel() * p.getExp()); - return getExpForLevel(p.getLevel()) + pointForCurrentLevel; + return getLeastExpForLevel(p.getLevel()) + pointForCurrentLevel; } - public static void subtractExpPoints(Player p, int points) { - addPlayerExperience(p, -points); + public static void subtractPlayerExpPoints(Player p, int points) { + addPlayerExpPoints(p, -points); } /** @@ -86,11 +86,11 @@ public static int getLevelForExp(int exp) { * if negative, then subtract from the player. * @throws IllegalArgumentException if the player ended with negative xp */ - public static void addPlayerExperience(Player p, int points) { + public static void addPlayerExpPoints(Player p, int points) { int playerPreviousExoPoints = getExpPoints(p); if (playerPreviousExoPoints < -points) throw new IllegalArgumentException("Negative Exp Left"); int newLevel = getLevelForExp(playerPreviousExoPoints + points); - int remPoint = playerPreviousExoPoints + points - getExpForLevel(newLevel); + int remPoint = playerPreviousExoPoints + points - getLeastExpForLevel(newLevel); p.setLevel(newLevel); p.setExp(0); p.giveExp(remPoint); diff --git a/src/main/java/cat/nyaa/ukit/xpstore/XpStoreFunction.java b/src/main/java/cat/nyaa/ukit/xpstore/XpStoreFunction.java index 51293e1..452aca4 100644 --- a/src/main/java/cat/nyaa/ukit/xpstore/XpStoreFunction.java +++ b/src/main/java/cat/nyaa/ukit/xpstore/XpStoreFunction.java @@ -21,6 +21,7 @@ import org.bukkit.event.entity.ExpBottleEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; @@ -33,7 +34,7 @@ public class XpStoreFunction implements SubCommandExecutor, SubTabCompleter, Lis private final NamespacedKey LoreLineIndexKey; private final String EXPBOTTLE_PERMISSION_NODE = "ukit.xpstore"; private final Map playerExpBottleMap = new HashMap<>(); - private final List subCommands = List.of("store", "take"); + private final List subCommands = List.of("store", "take", "supply"); public XpStoreFunction(SpigotLoader pluginInstance) { this.pluginInstance = pluginInstance; @@ -77,47 +78,102 @@ public boolean invokeCommand(CommandSender commandSender, Command command, Strin senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.notValidAmount.produce(Pair.of("input", args[1]))); return true; } - if (args[0].equalsIgnoreCase("store")) { - var expTotal = amountItem * amountInput; - if (ExperienceUtils.getExpPoints(senderPlayer) < expTotal) { - senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.notEnoughExp.produce( - Pair.of("expTotal", expTotal), - Pair.of("expPerBottle", amountInput), - Pair.of("amount", amountItem) - )); - return true; + switch (args[0].toLowerCase()) { + case "store" -> { + int amountPerBottle; + + if(args.length > 3) { + if ("level".equals(args[2])) { + amountPerBottle = ExperienceUtils.getLeastExpForLevel(amountInput); + } else { + amountPerBottle = amountInput; + } + } else { + amountPerBottle = amountInput; + } + + int expTotal = amountPerBottle * amountInput; + + if(ExperienceUtils.getExpPoints(senderPlayer) < expTotal) { + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.notEnoughExp.produce(Pair.of("amount", expTotal))); + return true; + } + + ItemStack itemSaved = addExpToItemStack(itemInHand, amountPerBottle); + Utils.setItemInHand(senderPlayer, Pair.of(itemSlot, itemSaved)); + ExperienceUtils.subtractPlayerExpPoints(senderPlayer, expTotal); + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.expSaved.produce(Pair.of("amount", expTotal))); + } + case "supply" -> { + int expMovedTotal; + if(args.length < 3 || !(args[2].equals("level"))) { + expMovedTotal = getMinimumDivisible(amountInput - ExperienceUtils.getExpPoints(senderPlayer), amountItem); + } else { + expMovedTotal = getMinimumDivisible(ExperienceUtils.getLeastExpForLevel(amountInput) - ExperienceUtils.getExpPoints(senderPlayer), amountItem); + } + int amountMovedAverage = expMovedTotal / amountItem; + int amountRemaining = getExpContained(itemInHand) - amountMovedAverage; + if(amountRemaining < 0) { + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.notEnoughExpInBottle.produce(Pair.of("amount", amountMovedAverage))); + return true; + } + ItemStack itemSaved = addExpToItemStack(itemInHand, -amountMovedAverage); + Utils.setItemInHand(senderPlayer, Pair.of(itemSlot, itemSaved)); + ExperienceUtils.addPlayerExpPoints(senderPlayer, expMovedTotal); + if(expMovedTotal <= 0) { + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.expSaved.produce(Pair.of("amount", -expMovedTotal))); + } else { + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.expTook.produce(Pair.of("amount", expMovedTotal))); + } } - var itemSaved = addExpToItemStack(itemInHand, amountInput); - Utils.setItemInHand(senderPlayer, Pair.of(itemSlot, itemSaved)); - ExperienceUtils.subtractExpPoints(senderPlayer, expTotal); - senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.expSaved.produce( - Pair.of("amount", expTotal) - )); - } else if (args[0].equalsIgnoreCase("take")) { - var expTotal = getMinimumDivisible(amountInput, amountItem); - var amountAverage = expTotal / amountItem; - var amountContained = getExpContained(itemInHand); - if (amountContained < amountAverage) { - senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.notEnoughExpInBottle.produce( - Pair.of("amount", amountAverage) - )); - return true; + case "take" -> { + int expTotal; + + if(args.length > 3) { + if ("level".equals(args[2])) { + expTotal = getMaximumDivisible(ExperienceUtils.getLeastExpForLevel(amountInput), amountItem); + } else { + expTotal = getMaximumDivisible(amountInput, amountItem); + } + } else { + expTotal = getMaximumDivisible(amountInput, amountItem); + } + + if(ExperienceUtils.getExpPoints(senderPlayer) < expTotal) { + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.notEnoughExp.produce(Pair.of("amount", expTotal))); + return true; + } + + int amountPerBottle = expTotal / amountItem; + int amountContained = getExpContained(itemInHand); + if (amountContained < amountPerBottle) { + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.notEnoughExpInBottle.produce(Pair.of("amount", amountPerBottle))); + return true; + } + ItemStack itemSaved = addExpToItemStack(itemInHand, -amountPerBottle); + Utils.setItemInHand(senderPlayer, Pair.of(itemSlot, itemSaved)); + ExperienceUtils.addPlayerExpPoints(senderPlayer, expTotal); + senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.expTook.produce(Pair.of("amount", expTotal))); } - var itemSaved = addExpToItemStack(itemInHand, -amountAverage); - Utils.setItemInHand(senderPlayer, Pair.of(itemSlot, itemSaved)); - ExperienceUtils.addPlayerExperience(senderPlayer, expTotal); - senderPlayer.sendMessage(pluginInstance.language.xpStoreLang.expTook.produce( - Pair.of("amount", expTotal) - )); } return true; } private int getMinimumDivisible(int amountExpect, int factor) { - if (amountExpect % factor == 0) + assert factor > 0; + if (amountExpect % factor == 0) { return amountExpect; - else - return amountExpect - (amountExpect % factor) + factor; + } else { + if(amountExpect > 0) { + return amountExpect - (amountExpect % factor) + factor; + } else { + return amountExpect - (amountExpect % factor); + } + } + } + + private int getMaximumDivisible(int amountExpect, int factor) { + return - getMinimumDivisible(-amountExpect, factor); } @Override @@ -133,22 +189,40 @@ public List tabComplete(CommandSender sender, Command command, String al if (args.length < 2) { return subCommands.stream().filter(t -> t.startsWith(args[0].toLowerCase())).toList(); } else if (args.length == 2) { - var item = Utils.getItemInHand(senderPlayer, Material.EXPERIENCE_BOTTLE); - if (args[0].equalsIgnoreCase("store")) { - if (item == null) - return List.of(pluginInstance.language.xpStoreLang.noExpBottleInHandTabNotice.produce()); - else - return List.of(String.valueOf(ExperienceUtils.getExpPoints(senderPlayer) / item.value().getAmount())); - } else if (args[0].equalsIgnoreCase("take")) { - if (item == null) - return List.of(pluginInstance.language.xpStoreLang.noExpBottleInHandTabNotice.produce()); - else - return List.of(String.valueOf(getExpContained(item.value()) * item.value().getAmount())); + Pair item = Utils.getItemInHand(senderPlayer, Material.EXPERIENCE_BOTTLE); + switch (args[0].toLowerCase()) { + case "store"-> { + if (item == null) { + return List.of(pluginInstance.language.xpStoreLang.noExpBottleInHandTabNotice.produce()); + } else { + return List.of(String.valueOf(ExperienceUtils.getExpPoints(senderPlayer) / item.value().getAmount())); + } + } + case "take" -> { + if (item == null) { + return List.of(pluginInstance.language.xpStoreLang.noExpBottleInHandTabNotice.produce()); + } else { + return List.of(String.valueOf(getExpContained(item.value()) * item.value().getAmount())); + } + } + case "supply" -> { + if (item == null) { + return List.of(pluginInstance.language.xpStoreLang.noExpBottleInHandTabNotice.produce()); + } else { + return List.of("0", "1", "30"); + } + } + default -> { + return List.of(); + } + } + + } else { + if(args.length == 3) { + return List.of("point", "level"); } else { return List.of(); } - } else { - return List.of(); } }