Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New command "u xp supply" and bug fix of adding and subtracting xp #3

Open
wants to merge 2 commits into
base: 1.18.2
Choose a base branch
from
Open
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
85 changes: 57 additions & 28 deletions src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -10,60 +10,89 @@ 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));
public static int getLeastExpForLevel(int level) {
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));
}

/**
* The true exp point for a player at this time.
*/
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) {
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);
public static void subtractPlayerExpPoints(Player p, int points) {
addPlayerExpPoints(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");
}

/**
* Change the player's experience (not experience level)
* 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 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 - getLeastExpForLevel(newLevel);
p.setLevel(newLevel);
p.setExp(0);
p.giveExp(remPoint);
}
}
168 changes: 121 additions & 47 deletions src/main/java/cat/nyaa/ukit/xpstore/XpStoreFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<UUID, Integer> playerExpBottleMap = new HashMap<>();
private final List<String> subCommands = List.of("store", "take");
private final List<String> subCommands = List.of("store", "take", "supply");

public XpStoreFunction(SpigotLoader pluginInstance) {
this.pluginInstance = pluginInstance;
Expand Down Expand Up @@ -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
Expand All @@ -133,22 +189,40 @@ public List<String> 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<EquipmentSlot, ItemStack> 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();
}
}

Expand Down