Release 2.7.2 (#233)

* Version 2.7.2
* Use Java 9's takeWhile
* Added placeholder %Level_[gamemode]_rank_value
Fixes https://github.com/BentoBoxWorld/Level/issues/228
* No save on disable (#231)
* Release 2.6.4
* Remove saving to database on disable.
https://github.com/BentoBoxWorld/Level/issues/229

First, the top ten tables are never actually used or loaded. They are
created in memory by loading the island levels. So there is no reason to
keep saving them.
Second, the island level data is saved every time it is changed, so
there is no need to save all of the cache on exit.

* Fixes tests
* Rosestacker (#232)
* Add support for RoseStacker 1.3.0
* Made plugin a Pladdon.
This commit is contained in:
tastybento 2021-08-15 08:56:21 -07:00 committed by GitHub
parent 389d06d0a2
commit 4a4794f771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 151 deletions

39
pom.xml
View File

@ -65,7 +65,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.7.0</build.version>
<build.version>2.7.2</build.version>
<sonar.projectKey>BentoBoxWorld_Level</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
@ -111,30 +111,6 @@
<build.number></build.number>
</properties>
</profile>
<profile>
<id>sonar</id>
<properties>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>bentobox-world</sonar.organization>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.6.0.1398</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sonar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
@ -155,6 +131,11 @@
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<!-- RoseStacker repo -->
<repository>
<id>rosewood-repo</id>
<url>https://repo.rosewooddev.io/repository/public/</url>
</repository>
</repositories>
<dependencies>
@ -195,6 +176,7 @@
<groupId>com.github.OmerBenGera</groupId>
<artifactId>WildStackerAPI</artifactId>
<version>b18</version>
<scope>provided</scope>
</dependency>
<!-- Static analysis -->
<!-- We are using Eclipse's annotations. If you're using IDEA, update
@ -209,6 +191,13 @@
<groupId>com.github.DeadSilenceIV</groupId>
<artifactId>AdvancedChestsAPI</artifactId>
<version>1.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dev.rosewood</groupId>
<artifactId>rosestacker</artifactId>
<version>1.3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -1,38 +0,0 @@
package world.bentobox.level;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Java 8 version of Java 9's forWhile
* https://www.baeldung.com/java-break-stream-foreach
* @author tastybento
*
* @param <T>
*/
public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
private Spliterator<T> splitr;
private Predicate<T> predicate;
private boolean isMatched = true;
public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {
super(splitr.estimateSize(), 0);
this.splitr = splitr;
this.predicate = predicate;
}
@Override
public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem) && isMatched) {
consumer.accept(elem);
} else {
isMatched = false;
}
});
return hadNext && isMatched;
}
}

View File

@ -36,6 +36,7 @@ import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.listeners.IslandActivitiesListeners;
import world.bentobox.level.listeners.JoinLeaveListener;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData;
import world.bentobox.level.requests.LevelRequestHandler;
import world.bentobox.level.requests.TopTenRequestHandler;
@ -56,6 +57,7 @@ public class Level extends Addon implements Listener {
private LevelsManager manager;
private boolean stackersEnabled;
private boolean advChestEnabled;
private boolean roseStackersEnabled;
private final List<GameModeAddon> registeredGameModes = new ArrayList<>();
@Override
@ -105,7 +107,7 @@ public class Level extends Addon implements Listener {
// Check if WildStackers is enabled on the server
// I only added support for counting blocks into the island level
// Someone else can PR if they want spawners added to the Leveling system :)
stackersEnabled = Bukkit.getPluginManager().getPlugin("WildStacker") != null;
stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker");
if (stackersEnabled) {
log("Hooked into WildStackers.");
}
@ -121,6 +123,11 @@ public class Level extends Addon implements Listener {
advChestEnabled = false;
}
}
// Check if RoseStackers is enabled
roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker");
if (roseStackersEnabled) {
log("Hooked into RoseStackers.");
}
}
/**
@ -206,6 +213,9 @@ public class Level extends Addon implements Listener {
gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank));
}
// Personal rank
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_rank_value", u -> getRankValue(gm.getOverWorld(), u));
}
String getRankName(World world, int rank) {
@ -228,8 +238,23 @@ public class Level extends Addon implements Listener {
.orElse(null));
}
/**
* Return the rank of the player in a world
* @param world world
* @param user player
* @return rank where 1 is the top rank.
*/
String getRankValue(World world, User user) {
if (user == null) {
return "";
}
// Get the island level for this user
long level = getManager().getIslandLevel(world, user.getUniqueId());
return String.valueOf(getManager().getTopTenLists().getOrDefault(world, new TopTenData(world)).getTopTen().values().stream().filter(l -> l > level).count() + 1);
}
String getVisitedIslandLevel(GameModeAddon gm, User user) {
if (!gm.inWorld(user.getLocation())) return "";
if (user == null || !gm.inWorld(user.getLocation())) return "";
return getIslands().getIslandAt(user.getLocation())
.map(island -> getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner()))
.orElse("0");
@ -256,11 +281,6 @@ public class Level extends Addon implements Listener {
public void onDisable() {
// Stop the pipeline
this.getPipeliner().stop();
// Save player data and the top tens
if (manager != null) {
manager.save();
}
}
private void loadBlockSettings() {
@ -431,4 +451,11 @@ public class Level extends Addon implements Listener {
return registeredGameModes.stream().map(GameModeAddon::getOverWorld).anyMatch(w -> Util.sameWorld(world, w));
}
/**
* @return the roseStackersEnabled
*/
public boolean isRoseStackersEnabled() {
return roseStackersEnabled;
}
}

View File

@ -14,10 +14,8 @@ import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@ -64,8 +62,6 @@ public class LevelsManager {
private final Database<IslandLevels> handler;
// A cache of island levels.
private final Map<String, IslandLevels> levelsCache;
private final Database<TopTenData> topTenHandler;
// Top ten lists
private final Map<World,TopTenData> topTenLists;
// Background
@ -79,8 +75,6 @@ public class LevelsManager {
// Set up the database handler to store and retrieve data
// Note that these are saved by the BentoBox database
handler = new Database<>(addon, IslandLevels.class);
// Top Ten handler
topTenHandler = new Database<>(addon, TopTenData.class);
// Initialize the cache
levelsCache = new HashMap<>();
// Initialize top ten lists
@ -181,8 +175,6 @@ public class LevelsManager {
}
// Save result
setIslandResults(island.getWorld(), island.getOwner(), r);
// Save top ten
addon.getManager().saveTopTen(island.getWorld());
// Save the island scan details
result.complete(r);
});
@ -444,18 +436,7 @@ public class LevelsManager {
.filter(e -> addon.getIslands().isOwner(world, e.getKey()))
.filter(l -> l.getValue() > 0)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
return takeWhile(stream, x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).collect(Collectors.toList()).size() + 1;
}
/**
* Java 8's version of Java 9's takeWhile
* @param stream
* @param predicate
* @return stream
*/
public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
return StreamSupport.stream(customSpliterator, false);
return stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).collect(Collectors.toList()).size() + 1;
}
/**
@ -475,15 +456,14 @@ public class LevelsManager {
void loadTopTens() {
topTenLists.clear();
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
addon.log("Generating Top Ten Tables");
addon.log("Generating rankings");
handler.loadObjects().forEach(il -> {
if (il.getLevel() > 0) {
addon.getIslands().getIslandById(il.getUniqueId()).ifPresent(i -> this.addToTopTen(i, il.getLevel()));
}
});
topTenLists.keySet().forEach(w -> {
addon.log("Loaded top ten for " + w.getName());
this.saveTopTen(w);
addon.log("Generated rankings for " + w.getName());
});
});
@ -497,27 +477,10 @@ public class LevelsManager {
public void removeEntry(World world, UUID uuid) {
if (topTenLists.containsKey(world)) {
topTenLists.get(world).getTopTen().remove(uuid);
topTenHandler.saveObjectAsync(topTenLists.get(world));
}
}
/**
* Saves all player data and the top ten
*/
public void save() {
levelsCache.values().forEach(handler::saveObjectAsync);
topTenLists.values().forEach(topTenHandler::saveObjectAsync);
}
/**
* Save the top ten for world
* @param world - world
*/
public void saveTopTen(World world) {
topTenHandler.saveObjectAsync(topTenLists.get(world));
}
/**
* Set an initial island level
* @param island - the island to set. Must have a non-null world

View File

@ -2,9 +2,12 @@ package world.bentobox.level.calculators;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -24,7 +27,6 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.bgsoftware.wildstacker.api.WildStackerAPI;
@ -33,6 +35,7 @@ import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets;
import dev.rosewood.rosestacker.api.RoseStackerAPI;
import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI;
import us.lynuxcraft.deadsilenceiv.advancedchests.chest.AdvancedChest;
import us.lynuxcraft.deadsilenceiv.advancedchests.chest.gui.page.ChestPage;
@ -151,6 +154,8 @@ public class IslandLevelCalculator {
private final Results results;
private long duration;
private final boolean zeroIsland;
private final Map<Environment, World> worlds = new EnumMap<>(Environment.class);
private final int seaHeight;
/**
* Constructor to get the level for an island
@ -170,6 +175,24 @@ public class IslandLevelCalculator {
this.limitCount = new HashMap<>(addon.getBlockConfig().getBlockLimits());
// Get the initial island level
results.initialLevel.set(addon.getInitialIslandLevel(island));
// Set up the worlds
worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld()));
// Nether
if (addon.getSettings().isNether()) {
World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
if (nether != null) {
worlds.put(Environment.NETHER, nether);
}
}
// End
if (addon.getSettings().isEnd()) {
World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
if (end != null) {
worlds.put(Environment.THE_END, end);
}
}
// Sea Height
seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld());
}
/**
@ -311,36 +334,33 @@ public class IslandLevelCalculator {
/**
* Get a chunk async
* @param world - the world where the chunk is
* @param env - the environment
* @param x - chunk x coordinate
* @param z - chunk z coordinate
* @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether
*/
private CompletableFuture<Chunk> getWorldChunk(@NonNull World world, Environment env, int x, int z) {
switch (env) {
case NETHER:
if (addon.getSettings().isNether()) {
World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
if (nether != null) {
return Util.getChunkAtAsync(nether, x, z, true);
private CompletableFuture<Chunk> getWorldChunk(Environment env, int x, int z) {
if (worlds.containsKey(env)) {
CompletableFuture<Chunk> r2 = new CompletableFuture<>();
// Get the chunk, and then coincidentally check the RoseStacker
Util.getChunkAtAsync(worlds.get(env), x, z, true).thenAccept(chunk -> roseStackerCheck(r2, chunk));
return r2;
}
}
// There is no chunk to scan, so return a null chunk
return CompletableFuture.completedFuture(null);
case THE_END:
if (addon.getSettings().isEnd()) {
World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
if (end != null) {
return Util.getChunkAtAsync(end, x, z, true);
}
}
// There is no chunk to scan, so return a null chunk
return CompletableFuture.completedFuture(null);
default:
return Util.getChunkAtAsync(world, x, z, true);
private void roseStackerCheck(CompletableFuture<Chunk> r2, Chunk chunk) {
if (addon.isRoseStackersEnabled()) {
RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> {
// Blocks below sea level can be scored differently
boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight;
// Check block once because the base block will be counted in the chunk snapshot
for (int _x = 0; _x < e.getStackSize() - 1; _x++) {
checkBlock(e.getBlock().getType(), belowSeaLevel);
}
});
}
r2.complete(chunk);
}
/**
@ -369,7 +389,6 @@ public class IslandLevelCalculator {
* @param chunkSnapshot - the chunk to scan
*/
private void scanAsync(CompletableFuture<Boolean> result, ChunkSnapshot chunkSnapshot, Chunk chunk) {
int seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld());
List<Vector> stackedBlocks = new ArrayList<>();
for (int x = 0; x< 16; x++) {
// Check if the block coordinate is inside the protection zone and if not, don't count it
@ -486,11 +505,11 @@ public class IslandLevelCalculator {
// Set up the result
CompletableFuture<Boolean> result = new CompletableFuture<>();
// Get chunks and scan
getWorldChunk(island.getWorld(), Environment.THE_END, p.x, p.z).thenAccept(endChunk ->
getWorldChunk(Environment.THE_END, p.x, p.z).thenAccept(endChunk ->
scanChunk(endChunk).thenAccept(b ->
getWorldChunk(island.getWorld(), Environment.NETHER, p.x, p.z).thenAccept(netherChunk ->
getWorldChunk(Environment.NETHER, p.x, p.z).thenAccept(netherChunk ->
scanChunk(netherChunk).thenAccept(b2 ->
getWorldChunk(island.getWorld(), Environment.NORMAL, p.x, p.z).thenAccept(normalChunk ->
getWorldChunk(Environment.NORMAL, p.x, p.z).thenAccept(normalChunk ->
scanChunk(normalChunk).thenAccept(b3 ->
// Complete the result now that all chunks have been scanned
result.complete(!chunksToCheck.isEmpty()))))

View File

@ -1,7 +1,7 @@
name: Level
name: Pladdon
main: world.bentobox.level.LevelPladdon
version: ${version}
api-version: "1.16"
api-version: "1.17"
description: Level Addon
author: tastybento
depend:

View File

@ -383,8 +383,8 @@ public class LevelsManagerTest {
Bukkit.getScheduler();
verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture());
task.getValue().run();
verify(addon).log(eq("Generating Top Ten Tables"));
verify(addon).log(eq("Loaded top ten for bskyblock-world"));
verify(addon).log(eq("Generating rankings"));
verify(addon).log(eq("Generated rankings for bskyblock-world"));
}
@ -401,14 +401,6 @@ public class LevelsManagerTest {
assertFalse(tt.containsKey(uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#save()}.
*/
@Test
public void testSave() {
lm.save();
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}.
*/