Skip to content

Commit

Permalink
(fix) exp bottle not release exp storage correctly sometime on thrown
Browse files Browse the repository at this point in the history
  • Loading branch information
Lori3f6 committed Sep 30, 2024
1 parent 39ec936 commit a272f5d
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 62 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@
<version>2.20.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
57 changes: 51 additions & 6 deletions src/main/java/cat/nyaa/ukit/utils/ExperienceUtils.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package cat.nyaa.ukit.utils;

import com.google.common.primitives.Ints;
import org.bukkit.Location;
import org.bukkit.entity.ExperienceOrb;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.util.Vector;

import java.util.List;
import java.util.Random;


public final class ExperienceUtils {
// From NyaaCore
// https://github.com/NyaaCat/NyaaCore/blob/0bc366debf51b0f4dcd867b657be19e14e772100/src/main/java/cat/nyaa/nyaacore/utils/ExperienceUtils.java

// refer to https://minecraft.wiki/w/Experience
private static final List<Integer> usableSplashExpList = List.of(1, 2, 6, 16, 36, 72, 148, 306, 616, 1236, 2476, 32767, 65535, 131071).reversed();
private static final Random random = new Random();

/**
* How much exp points at least needed to reach this level.
* i.e. getLevel() = level &amp;&amp; getExp() == 0
Expand Down Expand Up @@ -43,14 +55,47 @@ public static void subtractExpPoints(Player p, int points) {

/**
* Which level the player at if he/she has this mount of exp points
* TODO optimization
* refer <a href="https://minecraft.wiki/w/Experience">minecraft.wiki/w/Experience</a>
*/
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;
public static int getLevelForExp(Integer total) {
// 0-352
// 353-1507
// 1508+
return (int) switch (total) {
case Integer i when i < 353 -> Math.sqrt(total + 9.0) - 3.0;
case Integer i when i < 1508 ->
81.0 / 10.0 + Math.sqrt(2.0 / 5.0 * (total - 7839.0 / 40.0));
default ->
conditionalRounding(325.0 / 18.0 + Math.sqrt(2.0 / 9.0 * (total - 54215.0 / 72.0)));
};
}

public static double conditionalRounding(double value) {
double threshold = 1e-8;
long nearestInt = Math.round(value);
double diff = Math.abs(value - nearestInt);
return diff < threshold ? nearestInt : value;
}

public static void splashExp(int amount, Location location) {
while (amount > 0) {
var nextOrbValue = firstMatchedExp(amount);
var experienceOrb = location.getWorld().spawn(location, ExperienceOrb.class, CreatureSpawnEvent.SpawnReason.NATURAL);
experienceOrb.setExperience(nextOrbValue);
experienceOrb.setVelocity(randomVector().multiply(0.3));
amount -= nextOrbValue;
}
}

private static Vector randomVector() {
return new Vector(random.nextDouble() * 2 - 1, random.nextDouble() * 2 - 1, random.nextDouble() * 2 - 1);
}

private static int firstMatchedExp(int remaining) {
for (int i : usableSplashExpList) {
if (i <= remaining) return i;
}
throw new IllegalArgumentException("exp too large");
throw new IllegalStateException("shouldn't be here");
}

/**
Expand Down
66 changes: 10 additions & 56 deletions src/main/java/cat/nyaa/ukit/xpstore/XpStoreFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,23 @@
import org.bukkit.NamespacedKey;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.ThrownExpBottle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.ExpBottleEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.entity.ProjectileHitEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;

import java.util.*;
import java.util.ArrayList;
import java.util.List;

public class XpStoreFunction implements SubCommandExecutor, SubTabCompleter, Listener {
private final SpigotLoader pluginInstance;
private final NamespacedKey EXPAmountKey;
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");

public XpStoreFunction(SpigotLoader pluginInstance) {
Expand Down Expand Up @@ -166,10 +161,6 @@ private boolean isExpContainer(ItemStack itemStack) {
return itemStack.getItemMeta().getPersistentDataContainer().has(EXPAmountKey, PersistentDataType.INTEGER);
}

private boolean isExpContainer(Entity entity) {
return entity.getPersistentDataContainer().has(EXPAmountKey, PersistentDataType.INTEGER);
}

private int getExpContained(ItemStack itemStack) {
if (!isExpContainer(itemStack)) {
return 0;
Expand All @@ -178,13 +169,6 @@ private int getExpContained(ItemStack itemStack) {
}
}

private int getExpContained(Entity entity) {
if (!isExpContainer(entity))
return 0;
else
return entity.getPersistentDataContainer().get(EXPAmountKey, PersistentDataType.INTEGER);
}

private ItemStack addExpToItemStack(ItemStack itemStack, int amount) {
var itemMeta = itemStack.hasItemMeta() ? itemStack.getItemMeta() : Bukkit.getItemFactory().getItemMeta(itemStack.getType());
assert itemMeta != null;
Expand All @@ -198,16 +182,6 @@ private ItemStack addExpToItemStack(ItemStack itemStack, int amount) {
return itemStack;
}

private void addExpToEntity(Entity entity, int amount) {
if (!isExpContainer(entity)) {
entity.getPersistentDataContainer().set(EXPAmountKey, PersistentDataType.INTEGER, amount);
} else {
entity.getPersistentDataContainer().set(EXPAmountKey, PersistentDataType.INTEGER,
entity.getPersistentDataContainer().get(EXPAmountKey, PersistentDataType.INTEGER) + amount
);
}
}

private ItemMeta updateLore(ItemMeta itemMeta) {
var loreIndex = itemMeta.getPersistentDataContainer().get(LoreLineIndexKey, PersistentDataType.INTEGER);
var amount = itemMeta.getPersistentDataContainer().get(EXPAmountKey, PersistentDataType.INTEGER);
Expand Down Expand Up @@ -236,33 +210,13 @@ private ItemMeta updateLore(ItemMeta itemMeta) {
return itemMeta;
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteractWithExpBottle(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) {
return;
}
if (event.getItem() == null)
return;
playerExpBottleMap.put(event.getPlayer().getUniqueId(), getExpContained(event.getItem()));
}

@EventHandler(ignoreCancelled = true)
public void onThrewExpBottleLaunch(ProjectileLaunchEvent event) {
if (!(event.getEntity().getShooter() instanceof Player shooterPlayer))
return;
if (event.getEntity().getType() != EntityType.EXPERIENCE_BOTTLE)
return;
if (!playerExpBottleMap.containsKey(shooterPlayer.getUniqueId()))
return;
var amount = playerExpBottleMap.remove(shooterPlayer.getUniqueId());
addExpToEntity(event.getEntity(), amount);
}

@EventHandler(ignoreCancelled = true)
public void onExpBottleHitGround(ExpBottleEvent event) {
if (!(event.getEntity().getShooter() instanceof Player))
public void onExpBottleHit(ProjectileHitEvent event) {
if (!(event.getEntity() instanceof ThrownExpBottle thrownExpBottle))
return;
var amount = getExpContained(event.getEntity());
event.setExperience(event.getExperience() + amount);
var item = thrownExpBottle.getItem();
if (!isExpContainer(item)) return;
var expAmount = getExpContained(item);
ExperienceUtils.splashExp(expAmount, thrownExpBottle.getLocation());
}
}
31 changes: 31 additions & 0 deletions src/test/java/cat/nyaa/ukit/ExpUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cat.nyaa.ukit;

import cat.nyaa.ukit.utils.ExperienceUtils;
import org.junit.jupiter.api.Test;

import java.util.List;

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

public class ExpUtilTest {
@Test
public void testExpCalculationMatch() {
var levelsForTest = List.of(1, 2, 3, 5, 7, 9, 15, 16, 25, 30, 31, 40, 50, 70, 90, 120, 150, 200, 300, 500, 700, 1000, 2000, 4000, 8000, 1000, 12000, 15000, 18000, 21000);
// will break on lvl 21863 + 75705 offset with the total reach Integer.MAX_VALUE
for (int lvl : levelsForTest) {
var total = ExperienceUtils.getExpForLevel(lvl);
for (int offset = 0; offset < maxExpOffset(lvl); offset++) {
var result = ExperienceUtils.getLevelForExp(total + offset);
assertEquals(lvl, result);
}
}
}

private int maxExpOffset(Integer level) {
return switch (level) {
case Integer i when i < 16 -> 2 * level + 7;
case Integer i when i < 31 -> 5 * level - 38;
default -> 9 * level - 158;
};
}
}

0 comments on commit a272f5d

Please sign in to comment.