Compare commits

..

No commits in common. "develop" and "2.5.0" have entirely different histories.

63 changed files with 1903 additions and 7516 deletions

View File

@ -1,38 +0,0 @@
name: Build
on:
push:
branches:
- develop
- master
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 17
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar

22
.travis.yml Normal file
View File

@ -0,0 +1,22 @@
language: java
sudo: false
addons:
sonarcloud:
organization: "bentobox-world"
jdk:
- openjdk8
- openjdk11
matrix:
allow_failures:
- jdk: openjdk11
script:
# the following command line builds the project, runs the tests with coverage and then execute the SonarCloud analysis
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.projectKey=BentoBoxWorld_Level
cache:
directories:
- '$HOME/.m2/repository'
- '$HOME/.sonar/cache'

View File

@ -1,19 +1,9 @@
# Level # Level
[![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/Level)](https://ci.codemc.org/job/BentoBoxWorld/job/Level/)[ [![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/Level)](https://ci.codemc.org/job/BentoBoxWorld/job/Level/)
![Bugs](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=bugs)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level)
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=ncloc)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level)
## About Add-on for BentoBox to calculate island levels for BSkyBlock and AcidIsland. This add-on will work
Add-on for BentoBox to calculate island levels for BentoBox game modes like BSkyBlock and AcidIsland. It counts blocks and assigns a value to them.
Players gain levels by accumulating points and can lose levels too if their points go down. This add-on will work
for game modes listed in the config.yml. for game modes listed in the config.yml.
Full documentation for Level can be found at [docs.bentobox.world](https://docs.bentobox.world/en/latest/addons/Level/).
Official download releases are at [download.bentobox.world](download.bentobox.world).
## How to use ## How to use
1. Place the level addon jar in the addons folder of the BentoBox plugin 1. Place the level addon jar in the addons folder of the BentoBox plugin

200
pom.xml
View File

@ -54,27 +54,18 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version> <java.version>1.8</java.version>
<!-- Non-minecraft related dependencies --> <!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version> <powermock.version>2.0.2</powermock.version>
<!-- More visible way how to change dependency versions --> <!-- More visible way how to change dependency versions -->
<spigot.version>1.20.4-R0.1-SNAPSHOT</spigot.version> <spigot.version>1.16.1-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>2.4.0-SNAPSHOT</bentobox.version> <bentobox.version>1.14.0</bentobox.version>
<!-- Warps addon version -->
<warps.version>1.12.0</warps.version>
<!-- Visit addon version -->
<visit.version>1.6.0</visit.version>
<!-- Panel Utils version -->
<panelutils.version>1.1.0</panelutils.version>
<!-- Revision variable removes warning about dynamic version --> <!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision> <revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. --> <!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number> <build.number>-LOCAL</build.number>
<!-- This allows to change between versions. --> <!-- This allows to change between versions. -->
<build.version>2.14.0</build.version> <build.version>2.5.0</build.version>
<sonar.projectKey>BentoBoxWorld_Level</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties> </properties>
<!-- Profiles will allow to automatically change build version. --> <!-- Profiles will allow to automatically change build version. -->
@ -117,31 +108,33 @@
<build.number></build.number> <build.number></build.number>
</properties> </properties>
</profile> </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> </profiles>
<pluginRepositories>
<pluginRepository>
<id>apache.snapshots</id>
<url>https://repository.apache.org/snapshots/</url>
</pluginRepository>
</pluginRepositories>
<repositories> <repositories>
<!--Wild Stacker repo -->
<repository>
<id>bg-repo</id>
<url>https://repo.bg-software.com/repository/api/</url>
</repository>
<!-- RoseStacker repo -->
<repository>
<id>rosewood-repo</id>
<url>https://repo.rosewooddev.io/repository/public/</url>
</repository>
<!-- UltimateStacker repo -->
<repository>
<id>songoda-plugins</id>
<url>https://repo.songoda.com/repository/minecraft-plugins/</url>
</repository>
<repository> <repository>
<id>spigot-repo</id> <id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
@ -154,6 +147,7 @@
<id>codemc-public</id> <id>codemc-public</id>
<url>https://repo.codemc.org/repository/maven-public/</url> <url>https://repo.codemc.org/repository/maven-public/</url>
</repository> </repository>
<!--Wild Stacker repo -->
<repository> <repository>
<id>jitpack.io</id> <id>jitpack.io</id>
<url>https://jitpack.io</url> <url>https://jitpack.io</url>
@ -172,7 +166,7 @@
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<version>3.11.1</version> <version>3.0.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -193,29 +187,11 @@
<version>${bentobox.version}</version> <version>${bentobox.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>world.bentobox</groupId>
<artifactId>warps</artifactId>
<version>${warps.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>world.bentobox</groupId>
<artifactId>visit</artifactId>
<version>${visit.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>lv.id.bonne</groupId>
<artifactId>panelutils</artifactId>
<version>${panelutils.version}</version>
</dependency>
<!-- Wild Stacker dependency --> <!-- Wild Stacker dependency -->
<dependency> <dependency>
<groupId>com.bgsoftware</groupId> <groupId>com.github.OmerBenGera</groupId>
<artifactId>WildStackerAPI</artifactId> <artifactId>WildStackerAPI</artifactId>
<version>2023.3</version> <version>b17</version>
<scope>provided</scope>
</dependency> </dependency>
<!-- Static analysis --> <!-- Static analysis -->
<!-- We are using Eclipse's annotations. If you're using IDEA, update <!-- We are using Eclipse's annotations. If you're using IDEA, update
@ -224,26 +200,7 @@
<dependency> <dependency>
<groupId>org.eclipse.jdt</groupId> <groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.annotation</artifactId> <artifactId>org.eclipse.jdt.annotation</artifactId>
<version>2.2.600</version> <version>2.2.200</version>
</dependency>
<dependency>
<groupId>com.github.DeadSilenceIV</groupId>
<artifactId>AdvancedChestsAPI</artifactId>
<version>2.9-BETA</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dev.rosewood</groupId>
<artifactId>rosestacker</artifactId>
<version>1.3.0</version>
<scope>provided</scope>
</dependency>
<!-- Ultimate Stacker dependency -->
<dependency>
<groupId>com.craftaro</groupId>
<artifactId>UltimateStacker-API</artifactId>
<version>1.0.0-20240329.173606-35</version>
<scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@ -284,47 +241,16 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version> <version>3.7.0</version>
<configuration> <configuration>
<release>${java.version}</release> <source>${java.version}</source>
<target>${java.version}</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version> <version>2.22.0</version>
<configuration>
<argLine>
${argLine}
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens
java.base/java.util.stream=ALL-UNNAMED
--add-opens java.base/java.text=ALL-UNNAMED
--add-opens
java.base/java.util.regex=ALL-UNNAMED
--add-opens
java.base/java.nio.channels.spi=ALL-UNNAMED
--add-opens java.base/sun.nio.ch=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens
java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/sun.nio.fs=ALL-UNNAMED
--add-opens java.base/sun.nio.cs=ALL-UNNAMED
--add-opens java.base/java.nio.file=ALL-UNNAMED
--add-opens
java.base/java.nio.charset=ALL-UNNAMED
--add-opens
java.base/java.lang.reflect=ALL-UNNAMED
--add-opens
java.logging/java.util.logging=ALL-UNNAMED
--add-opens java.base/java.lang.ref=ALL-UNNAMED
--add-opens java.base/java.util.jar=ALL-UNNAMED
--add-opens java.base/java.util.zip=ALL-UNNAMED
</argLine>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -336,10 +262,11 @@
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version> <version>3.0.1</version>
<configuration> <configuration>
<doclint>none</doclint> <!-- Turnoff all checks --> <show>private</show>
<failOnError>false</failOnError> <failOnError>false</failOnError>
<additionalJOption>-Xdoclint:none</additionalJOption>
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable> <javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
<source>17</source> <source>8</source>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
@ -373,69 +300,30 @@
<artifactId>maven-deploy-plugin</artifactId> <artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version> <version>2.8.2</version>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.1-SNAPSHOT</version>
<configuration>
<minimizeJar>true</minimizeJar>
<artifactSet>
<includes>
<include>lv.id.bonne:panelutils:*</include>
</includes>
</artifactSet>
<transformers>
<!-- Add a transformer to exclude any other manifest files (possibly from dependencies). -->
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resource>MANIFEST.MF</resource>
</transformer>
<!-- Add a transformer to include your custom manifest file. -->
<transformer implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
<resource>META-INF/MANIFEST.MF</resource>
<file>src/main/resources/META-INF/MANIFEST.MF</file>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version> <version>0.8.3</version>
<configuration> <configuration>
<append>true</append> <append>true</append>
<excludes> <excludes>
<!-- This is required to prevent Jacoco from adding <!-- This is required to prevent Jacoco from adding
synthetic fields to a JavaBean class (causes errors in testing) --> synthetic fields to a JavaBean class (causes errors in testing) -->
<exclude>**/*Names*</exclude> <exclude>**/*Names*</exclude>
<!-- Prevents the Material is too large to mock error -->
<exclude>org/bukkit/Material*</exclude>
</excludes> </excludes>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
<id>prepare-agent</id> <id>pre-unit-test</id>
<goals> <goals>
<goal>prepare-agent</goal> <goal>prepare-agent</goal>
</goals> </goals>
</execution> </execution>
<execution> <execution>
<id>report</id> <id>post-unit-test</id>
<goals> <goals>
<goal>report</goal> <goal>report</goal>
</goals> </goals>
<configuration>
<formats>
<format>XML</format>
</formats>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -2,29 +2,27 @@ package world.bentobox.level;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.configuration.Config; import world.bentobox.bentobox.api.configuration.Config;
import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.calculators.Pipeliner; import world.bentobox.level.calculators.Pipeliner;
import world.bentobox.level.commands.AdminLevelCommand; import world.bentobox.level.commands.AdminLevelCommand;
import world.bentobox.level.commands.AdminLevelStatusCommand; import world.bentobox.level.commands.AdminLevelStatusCommand;
import world.bentobox.level.commands.AdminSetInitialLevelCommand; import world.bentobox.level.commands.AdminSetInitialLevelCommand;
import world.bentobox.level.commands.AdminStatsCommand;
import world.bentobox.level.commands.AdminTopCommand; import world.bentobox.level.commands.AdminTopCommand;
import world.bentobox.level.commands.IslandLevelCommand; import world.bentobox.level.commands.IslandLevelCommand;
import world.bentobox.level.commands.IslandTopCommand; import world.bentobox.level.commands.IslandTopCommand;
@ -33,21 +31,15 @@ import world.bentobox.level.config.BlockConfig;
import world.bentobox.level.config.ConfigSettings; import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.IslandActivitiesListeners;
import world.bentobox.level.listeners.JoinLeaveListener; import world.bentobox.level.listeners.JoinLeaveListener;
import world.bentobox.level.listeners.MigrationListener;
import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.requests.LevelRequestHandler; import world.bentobox.level.requests.LevelRequestHandler;
import world.bentobox.level.requests.TopTenRequestHandler; import world.bentobox.level.requests.TopTenRequestHandler;
import world.bentobox.visit.VisitAddon;
import world.bentobox.warps.Warp;
/** /**
* @author tastybento * @author tastybento
* *
*/ */
public class Level extends Addon { public class Level extends Addon implements Listener {
// The 10 in top ten
public static final int TEN = 10;
// Settings // Settings
private ConfigSettings settings; private ConfigSettings settings;
@ -56,20 +48,6 @@ public class Level extends Addon {
private Pipeliner pipeliner; private Pipeliner pipeliner;
private LevelsManager manager; private LevelsManager manager;
private boolean stackersEnabled; private boolean stackersEnabled;
private boolean advChestEnabled;
private boolean roseStackersEnabled;
private boolean ultimateStackerEnabled;
private final List<GameModeAddon> registeredGameModes = new ArrayList<>();
/**
* Local variable that stores if warpHook is present.
*/
private Warp warpHook;
/**
* Local variable that stores if visitHook is present.
*/
private VisitAddon visitHook;
@Override @Override
public void onLoad() { public void onLoad() {
@ -82,17 +60,11 @@ public class Level extends Addon {
} else { } else {
configObject.saveConfigObject(settings); configObject.saveConfigObject(settings);
} }
// Save existing panels.
this.saveResource("panels/top_panel.yml", false);
this.saveResource("panels/detail_panel.yml", false);
this.saveResource("panels/value_panel.yml", false);
} }
private boolean loadSettings() { private boolean loadSettings() {
// Load settings again to get worlds // Load settings again to get worlds
settings = configObject.loadConfigObject(); settings = configObject.loadConfigObject();
return settings == null; return settings == null;
} }
@ -106,16 +78,14 @@ public class Level extends Addon {
// Register listeners // Register listeners
this.registerListener(new IslandActivitiesListeners(this)); this.registerListener(new IslandActivitiesListeners(this));
this.registerListener(new JoinLeaveListener(this)); this.registerListener(new JoinLeaveListener(this));
this.registerListener(new MigrationListener(this)); this.registerListener(this);
// Register commands for GameModes // Register commands for GameModes
registeredGameModes.clear();
getPlugin().getAddonsManager().getGameModeAddons().stream() getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> { .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
log("Level hooking into " + gm.getDescription().getName()); log("Level hooking into " + gm.getDescription().getName());
registerCommands(gm); registerCommands(gm);
new PlaceholderManager(this).registerPlaceholders(gm); registerPlaceholders(gm);
registeredGameModes.add(gm);
}); });
// Register request handlers // Register request handlers
registerRequestHandler(new LevelRequestHandler(this)); registerRequestHandler(new LevelRequestHandler(this));
@ -124,97 +94,97 @@ public class Level extends Addon {
// Check if WildStackers is enabled on the server // Check if WildStackers is enabled on the server
// I only added support for counting blocks into the island level // I only added support for counting blocks into the island level
// Someone else can PR if they want spawners added to the Leveling system :) // Someone else can PR if they want spawners added to the Leveling system :)
if (!settings.getDisabledPluginHooks().contains("WildStacker")) { stackersEnabled = Bukkit.getPluginManager().getPlugin("WildStacker") != null;
stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker");
if (stackersEnabled) {
log("Hooked into WildStackers.");
}
} }
// Check if AdvancedChests is enabled on the server @EventHandler
if (!settings.getDisabledPluginHooks().contains("AdvancedChests")) { public void onBentoBoxReady(BentoBoxReadyEvent e) {
Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); // Perform upgrade check
advChestEnabled = advChest != null; manager.migrate();
if (advChestEnabled) { // Load TopTens
// Check version manager.loadTopTens();
if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { /*
log("Hooked into AdvancedChests."); * DEBUG code to generate fake islands and then try to level them all.
} else { Bukkit.getScheduler().runTaskLater(getPlugin(), () -> {
logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() getPlugin().getAddonsManager().getGameModeAddons().stream()
+ " - requires version 23.0 or later"); .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
advChestEnabled = false; .forEach(gm -> {
for (int i = 0; i < 1000; i++) {
try {
NewIsland.builder().addon(gm).player(User.getInstance(UUID.randomUUID())).name("default").reason(Reason.CREATE).noPaste().build();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} }
} }
});
// Queue all islands DEBUG
getIslands().getIslands().stream().filter(Island::isOwned).forEach(is -> {
this.getManager().calculateLevel(is.getOwner(), is).thenAccept(r ->
log("Result for island calc " + r.getLevel() + " at " + is.getCenter()));
});
}, 60L);*/
} }
// Check if RoseStackers is enabled
if (!settings.getDisabledPluginHooks().contains("RoseStacker")) { private void registerPlaceholders(GameModeAddon gm) {
roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); if (getPlugin().getPlaceholdersManager() == null) return;
if (roseStackersEnabled) { // Island Level
log("Hooked into RoseStackers."); getPlugin().getPlaceholdersManager().registerPlaceholder(this,
} gm.getDescription().getName().toLowerCase() + "_island_level",
user -> getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId()));
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_points_to_next_level",
user -> getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId()));
// Visited Island Level
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_visited_island_level", user -> getVisitedIslandLevel(gm, user));
// Register Top Ten Placeholders
for (int i = 1; i < 11; i++) {
final int rank = i;
// Name
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_top_name_" + i, u -> getRankName(gm.getOverWorld(), rank));
// Level
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank));
} }
// Check if UltimateStacker is enabled
if (!settings.getDisabledPluginHooks().contains("UltimateStacker")) {
ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker");
if (ultimateStackerEnabled) {
log("Hooked into UltimateStacker.");
}
}
} }
@Override String getRankName(World world, int rank) {
public void allLoaded() { if (rank < 1) rank = 1;
super.allLoaded(); if (rank > 10) rank = 10;
return getPlayers().getName(getManager().getTopTen(world, 10).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null));
if (this.isEnabled()) {
this.hookExtensions();
}
} }
/** String getRankLevel(World world, int rank) {
* This method tries to hook into addons and plugins if (rank < 1) rank = 1;
*/ if (rank > 10) rank = 10;
private void hookExtensions() { return getManager()
// Try to find Visit addon and if it does not exist, display a warning .formatLevel(getManager()
this.getAddonByName("Visit").ifPresentOrElse(addon -> { .getTopTen(world, 10)
this.visitHook = (VisitAddon) addon; .values()
this.log("Level Addon hooked into Visit addon."); .stream()
}, () -> this.visitHook = null); .skip(rank - 1L)
.limit(1L)
// Try to find Warps addon and if it does not exist, display a warning .findFirst()
this.getAddonByName("Warps").ifPresentOrElse(addon -> { .orElse(null));
this.warpHook = (Warp) addon;
this.log("Level Addon hooked into Warps addon.");
}, () -> this.warpHook = null);
} }
/** String getVisitedIslandLevel(GameModeAddon gm, User user) {
* Compares versions if (!gm.inWorld(user.getLocation())) return "";
* return getIslands().getIslandAt(user.getLocation())
* @param version1 version 1 .map(island -> getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner()))
* @param version2 version 2 .orElse("0");
* @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2} }
*/
public static int compareVersions(String version1, String version2) {
int comparisonResult = 0;
String[] version1Splits = version1.split("\\.");
String[] version2Splits = version2.split("\\.");
int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length);
for (int i = 0; i < maxLengthOfVersionSplits; i++) {
Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0;
Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0;
int compare = v1.compareTo(v2);
if (compare != 0) {
comparisonResult = compare;
break;
}
}
return comparisonResult;
}
private void registerCommands(GameModeAddon gm) { private void registerCommands(GameModeAddon gm) {
gm.getAdminCommand().ifPresent(adminCommand -> { gm.getAdminCommand().ifPresent(adminCommand -> {
@ -224,7 +194,6 @@ public class Level extends Addon {
if (getSettings().isZeroNewIslandLevels()) { if (getSettings().isZeroNewIslandLevels()) {
new AdminSetInitialLevelCommand(this, adminCommand); new AdminSetInitialLevelCommand(this, adminCommand);
} }
new AdminStatsCommand(this, adminCommand);
}); });
gm.getPlayerCommand().ifPresent(playerCmd -> { gm.getPlayerCommand().ifPresent(playerCmd -> {
new IslandLevelCommand(this, playerCmd); new IslandLevelCommand(this, playerCmd);
@ -237,6 +206,11 @@ public class Level extends Addon {
public void onDisable() { public void onDisable() {
// Stop the pipeline // Stop the pipeline
this.getPipeliner().stop(); this.getPipeliner().stop();
// Save player data and the top tens
if (manager != null) {
manager.save();
}
} }
private void loadBlockSettings() { private void loadBlockSettings() {
@ -257,6 +231,7 @@ public class Level extends Addon {
} }
/** /**
* @return the blockConfig * @return the blockConfig
*/ */
@ -287,7 +262,6 @@ public class Level extends Addon {
/** /**
* Set the config settings - used for tests only * Set the config settings - used for tests only
*
* @param configSettings - config settings * @param configSettings - config settings
*/ */
void setSettings(ConfigSettings configSettings) { void setSettings(ConfigSettings configSettings) {
@ -302,16 +276,8 @@ public class Level extends Addon {
return stackersEnabled; return stackersEnabled;
} }
/**
* @return the advChestEnabled
*/
public boolean isAdvChestEnabled() {
return advChestEnabled;
}
/** /**
* Get level from cache for a player. * Get level from cache for a player.
*
* @param targetPlayer - target player UUID * @param targetPlayer - target player UUID
* @return Level of player or zero if player is unknown or UUID is null * @return Level of player or zero if player is unknown or UUID is null
*/ */
@ -321,7 +287,6 @@ public class Level extends Addon {
/** /**
* Sets the player's level to a value * Sets the player's level to a value
*
* @param world - world * @param world - world
* @param targetPlayer - target player * @param targetPlayer - target player
* @param level - level * @param level - level
@ -332,7 +297,6 @@ public class Level extends Addon {
/** /**
* Zeros the initial island level * Zeros the initial island level
*
* @param island - island * @param island - island
* @param level - initial calculated island level * @param level - initial calculated island level
*/ */
@ -342,7 +306,6 @@ public class Level extends Addon {
/** /**
* Get the initial island level * Get the initial island level
*
* @param island - island * @param island - island
* @return level or 0 by default * @return level or 0 by default
*/ */
@ -352,32 +315,29 @@ public class Level extends Addon {
/** /**
* Calculates a user's island * Calculates a user's island
*
* @param world - the world where this island is * @param world - the world where this island is
* @param user - not used! See depecration message * @param user - not used! See depecration message
* @param playerUUID - the target island member's UUID * @param playerUUID - the target island member's UUID
* @deprecated Do not use this anymore. Use * @deprecated Do not use this anymore. Use getManager().calculateLevel(playerUUID, island)
* getManager().calculateLevel(playerUUID, island)
*/ */
@Deprecated(since = "2.3.0", forRemoval = true) @Deprecated
public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) {
Island island = getIslands().getIsland(world, playerUUID); Island island = getIslands().getIsland(world, playerUUID);
if (island != null) if (island != null) getManager().calculateLevel(playerUUID, island);
getManager().calculateLevel(playerUUID, island);
} }
/** /**
* Provide the levels data for the target player * Provide the levels data for the target player
*
* @param targetPlayer - UUID of target player * @param targetPlayer - UUID of target player
* @return LevelsData object or null if not found. Only island levels are set! * @return LevelsData object or null if not found. Only island levels are set!
* @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)} * @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)}
*/ */
@Deprecated(since = "2.3.0", forRemoval = true) @Deprecated
public LevelsData getLevelsData(UUID targetPlayer) { public LevelsData getLevelsData(UUID targetPlayer) {
LevelsData ld = new LevelsData(targetPlayer); LevelsData ld = new LevelsData(targetPlayer);
getPlugin().getAddonsManager().getGameModeAddons().stream() getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> { .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
if (getSettings().isZeroNewIslandLevels()) { if (getSettings().isZeroNewIslandLevels()) {
Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer); Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer);
if (island != null) { if (island != null) {
@ -388,64 +348,4 @@ public class Level extends Addon {
}); });
return ld; return ld;
} }
/**
* @return the registeredGameModes
*/
public List<GameModeAddon> getRegisteredGameModes() {
return registeredGameModes;
}
/**
* Check if Level addon is active in game mode
*
* @param gm Game Mode Addon
* @return true if active, false if not
*/
public boolean isRegisteredGameMode(GameModeAddon gm) {
return registeredGameModes.contains(gm);
}
/**
* Checks if Level addon is active in world
*
* @param world world
* @return true if active, false if not
*/
public boolean isRegisteredGameModeWorld(World world) {
return registeredGameModes.stream().map(GameModeAddon::getOverWorld).anyMatch(w -> Util.sameWorld(world, w));
}
/**
* @return the roseStackersEnabled
*/
public boolean isRoseStackersEnabled() {
return roseStackersEnabled;
}
/**
* @return the ultimateStackerEnabled
*/
public boolean isUltimateStackerEnabled() {
return ultimateStackerEnabled;
}
/**
* Method Level#getVisitHook returns the visitHook of this object.
*
* @return {@code Visit} of this object, {@code null} otherwise.
*/
public VisitAddon getVisitHook() {
return this.visitHook;
}
/**
* Method Level#getWarpHook returns the warpHook of this object.
*
* @return {@code Warp} of this object, {@code null} otherwise.
*/
public Warp getWarpHook() {
return this.warpHook;
}
} }

View File

@ -1,16 +0,0 @@
package world.bentobox.level;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.Pladdon;
/**
* @author tastybento
*
*/
public class LevelPladdon extends Pladdon {
@Override
public Addon getAddon() {
return new Level();
}
}

View File

@ -2,11 +2,11 @@ package world.bentobox.level;
import java.math.BigInteger; import java.math.BigInteger;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.time.Instant; import java.util.ArrayList;
import java.util.AbstractMap;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
@ -15,15 +15,21 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database; import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.calculators.Results; import world.bentobox.level.calculators.Results;
@ -32,11 +38,13 @@ import world.bentobox.level.events.IslandPreLevelEvent;
import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData; import world.bentobox.level.objects.TopTenData;
import world.bentobox.level.util.CachedData; import world.bentobox.level.panels.DetailsGUITab;
import world.bentobox.level.panels.DetailsGUITab.DetailsType;
public class LevelsManager { public class LevelsManager {
private static final String INTOPTEN = "intopten"; private static final String INTOPTEN = "intopten";
private static final TreeMap<BigInteger, String> LEVELS; private static final TreeMap<BigInteger, String> LEVELS;
private static final int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
private static final BigInteger THOUSAND = BigInteger.valueOf(1000); private static final BigInteger THOUSAND = BigInteger.valueOf(1000);
static { static {
LEVELS = new TreeMap<>(); LEVELS = new TreeMap<>();
@ -46,16 +54,21 @@ public class LevelsManager {
LEVELS.put(THOUSAND.pow(3), "G"); LEVELS.put(THOUSAND.pow(3), "G");
LEVELS.put(THOUSAND.pow(4), "T"); LEVELS.put(THOUSAND.pow(4), "T");
} }
private final Level addon; private Level addon;
// Database handler for level data // Database handler for level data
private final Database<IslandLevels> handler; private final Database<IslandLevels> handler;
// A cache of island levels. // A cache of island levels.
private final Map<String, IslandLevels> levelsCache; private final Map<String, IslandLevels> levelsCache;
private final Database<TopTenData> topTenHandler;
// Top ten lists // Top ten lists
private final Map<World,TopTenData> topTenLists; private final Map<World,TopTenData> topTenLists;
// Cache for top tens // Background
private Map<World, CachedData> cache = new HashMap<>(); private final PanelItem background;
public LevelsManager(Level addon) { public LevelsManager(Level addon) {
this.addon = addon; this.addon = addon;
@ -63,10 +76,14 @@ public class LevelsManager {
// Set up the database handler to store and retrieve data // Set up the database handler to store and retrieve data
// Note that these are saved by the BentoBox database // Note that these are saved by the BentoBox database
handler = new Database<>(addon, IslandLevels.class); handler = new Database<>(addon, IslandLevels.class);
// Top Ten handler
topTenHandler = new Database<>(addon, TopTenData.class);
// Initialize the cache // Initialize the cache
levelsCache = new HashMap<>(); levelsCache = new HashMap<>();
// Initialize top ten lists // Initialize top ten lists
topTenLists = new ConcurrentHashMap<>(); topTenLists = new ConcurrentHashMap<>();
// Background
background = new PanelItemBuilder().icon(Material.BLACK_STAINED_GLASS_PANE).name(" ").build();
} }
public void migrate() { public void migrate() {
@ -79,7 +96,8 @@ public class LevelsManager {
// World // World
.map(Bukkit::getWorld).filter(Objects::nonNull) .map(Bukkit::getWorld).filter(Objects::nonNull)
// Island // Island
.map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> { .map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull)
.forEach(i -> {
// Make new database entry // Make new database entry
World w = i.getWorld(); World w = i.getWorld();
IslandLevels il = new IslandLevels(i.getUniqueId()); IslandLevels il = new IslandLevels(i.getUniqueId());
@ -101,26 +119,43 @@ public class LevelsManager {
}); });
} }
/**
* Add a score to the top players list
* @param world - world
* @param targetPlayer - target player
* @param lv - island level
*/
private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
// Get top ten
Map<UUID, Long> topTen = topTenLists.computeIfAbsent(world, k -> new TopTenData(world)).getTopTen();
// Remove this player from the top list no matter what (we'll put them back later if required)
topTen.remove(targetPlayer);
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
if (island != null && island.getOwner() != null && hasTopTenPerm(world, island.getOwner())) {
// Insert the owner into the top ten
topTen.put(island.getOwner(), lv);
}
}
/** /**
* Add an island to a top ten * Add an island to a top ten
*
* @param island - island to add * @param island - island to add
* @param lv - level * @param lv - level
* @return true if successful, false if not added * @return true if successful, false if not added
*/ */
private boolean addToTopTen(Island island, long lv) { private boolean addToTopTen(Island island, long lv) {
if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) { if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) {
topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen() topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld()))
.put(island.getUniqueId(), lv); .getTopTen().put(island.getOwner(), lv);
return true; return true;
} }
return false; return false;
} }
/** /**
* Calculate the island level, set all island member's levels to the result and * Calculate the island level, set all island member's levels to the result and try to add the owner to the top ten
* try to add the owner to the top ten
*
* @param targetPlayer - uuid of targeted player - owner or team member * @param targetPlayer - uuid of targeted player - owner or team member
* @param island - island to calculate * @param island - island to calculate
* @return completable future with the results of the calculation * @return completable future with the results of the calculation
@ -135,13 +170,16 @@ public class LevelsManager {
} }
// Add island to the pipeline // Add island to the pipeline
addon.getPipeliner().addIsland(island).thenAccept(r -> { addon.getPipeliner().addIsland(island).thenAccept(r -> {
// Results are irrelevant because the island is unowned or deleted, or // Results are irrelevant because the island is unowned or deleted, or IslandLevelCalcEvent is cancelled
// IslandLevelCalcEvent is cancelled
if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) { if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) {
System.out.println("results are null or event canceled");
result.complete(null); result.complete(null);
} }
// Save result // Save result
setIslandResults(island, r); setIslandResults(island.getWorld(), island.getOwner(), r);
// Save top ten
addon.getManager().saveTopTen(island.getWorld());
// Save the island scan details // Save the island scan details
result.complete(r); result.complete(r);
}); });
@ -150,7 +188,6 @@ public class LevelsManager {
/** /**
* Fires the IslandLevelCalculatedEvent and returns true if it is canceled * Fires the IslandLevelCalculatedEvent and returns true if it is canceled
*
* @param targetPlayer - target player * @param targetPlayer - target player
* @param island - island * @param island - island
* @param results - results set * @param results - results set
@ -160,28 +197,22 @@ public class LevelsManager {
// Fire post calculation event // Fire post calculation event
IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results); IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results);
Bukkit.getPluginManager().callEvent(ilce); Bukkit.getPluginManager().callEvent(ilce);
if (ilce.isCancelled()) if (ilce.isCancelled()) return true;
return true;
// Set the values if they were altered // Set the values if they were altered
results.setLevel((Long)ilce.getKeyValues().getOrDefault("level", results.getLevel())); results.setLevel((Long)ilce.getKeyValues().getOrDefault("level", results.getLevel()));
results.setInitialLevel((Long)ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel())); results.setInitialLevel((Long)ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel()));
results.setDeathHandicap((int)ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap())); results.setDeathHandicap((int)ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap()));
results.setPointsToNextLevel( results.setPointsToNextLevel((Long)ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel()));
(Long) ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel()));
results.setTotalPoints((Long) ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints()));
return ((Boolean)ilce.getKeyValues().getOrDefault("isCancelled", false)); return ((Boolean)ilce.getKeyValues().getOrDefault("isCancelled", false));
} }
/** /**
* Get the string representation of the level. May be converted to shorthand * Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k
* notation, e.g., 104556 = 10.5k
*
* @param lvl - long value to represent * @param lvl - long value to represent
* @return string of the level. * @return string of the level.
*/ */
public String formatLevel(@Nullable Long lvl) { public String formatLevel(@Nullable Long lvl) {
if (lvl == null) if (lvl == null) return "";
return "";
String level = String.valueOf(lvl); String level = String.valueOf(lvl);
// Asking for the level of another player // Asking for the level of another player
if(addon.getSettings().isShorthand()) { if(addon.getSettings().isShorthand()) {
@ -194,16 +225,110 @@ public class LevelsManager {
// 1 527 314 -> 1.5M // 1 527 314 -> 1.5M
// 3 874 130 021 -> 3.8G // 3 874 130 021 -> 3.8G
// 4 002 317 889 -> 4.0T // 4 002 317 889 -> 4.0T
level = new DecimalFormat("#.#").format( level = new DecimalFormat("#.#").format(levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue()/1000.0) + stage.getValue();
levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue();
} }
} }
return level; return level;
} }
/**
* Displays the Top Ten list
* @param world - world
* @param user - the requesting player
*/
public void getGUI(World world, final User user) {
// Check world
Map<UUID, Long> topTen = getTopTen(world, 10);
PanelBuilder panel = new PanelBuilder()
.name(user.getTranslation("island.top.gui-title"))
.size(54)
.user(user);
// Background
for (int j = 0; j < 54; panel.item(j++, background));
// Top Ten
int i = 0;
boolean inTopTen = false;
for (Entry<UUID, Long> m : topTen.entrySet()) {
PanelItem h = getHead((i+1), m.getValue(), m.getKey(), user, world);
panel.item(SLOTS[i], h);
// If this is also the asking player
if (m.getKey().equals(user.getUniqueId())) {
inTopTen = true;
addSelf(world, user, panel);
}
i++;
}
// Show remaining slots
for (; i < SLOTS.length; i++) {
panel.item(SLOTS[i], new PanelItemBuilder().icon(Material.GREEN_STAINED_GLASS_PANE).name(String.valueOf(i + 1)).build());
}
// Add yourself if you were not already in the top ten
if (!inTopTen) {
addSelf(world, user, panel);
}
panel.build();
}
private void addSelf(World world, User user, PanelBuilder panel) {
if (addon.getIslands().hasIsland(world, user) || addon.getIslands().inTeam(world, user.getUniqueId())) {
PanelItem head = getHead(0, this.getIslandLevel(world, user.getUniqueId()), user.getUniqueId(), user, world);
setClickHandler(head, user, world);
panel.item(49, head);
}
}
private void setClickHandler(PanelItem head, User user, World world) {
head.setClickHandler((p, u, ch, s) -> {
new TabbedPanelBuilder()
.user(user)
.world(world)
.tab(1, new DetailsGUITab(addon, world, user, DetailsType.ALL_BLOCKS))
.tab(2, new DetailsGUITab(addon, world, user, DetailsType.ABOVE_SEA_LEVEL_BLOCKS))
.tab(3, new DetailsGUITab(addon, world, user, DetailsType.UNDERWATER_BLOCKS))
.tab(4, new DetailsGUITab(addon, world, user, DetailsType.SPAWNERS))
.startingSlot(1)
.size(54)
.build().openPanel();
return true;
});
}
/**
* Get the head panel item
* @param rank - the top ten rank of this player/team. Can be used in the name of the island for vanity.
* @param level - the level of the island
* @param playerUUID - the UUID of the top ten player
* @param asker - the asker of the top ten
* @return PanelItem
*/
private PanelItem getHead(int rank, long level, UUID playerUUID, User asker, World world) {
final String name = addon.getPlayers().getName(playerUUID);
List<String> description = new ArrayList<>();
if (rank > 0) {
description.add(asker.getTranslation("island.top.gui-heading", "[name]", name, "[rank]", String.valueOf(rank)));
}
description.add(asker.getTranslation("island.top.island-level","[level]", formatLevel(level)));
if (addon.getIslands().inTeam(world, playerUUID)) {
List<String> memberList = new ArrayList<>();
for (UUID members : addon.getIslands().getMembers(world, playerUUID)) {
memberList.add(ChatColor.AQUA + addon.getPlayers().getName(members));
}
description.addAll(memberList);
}
PanelItemBuilder builder = new PanelItemBuilder()
.icon(name)
.name(name)
.description(description);
return builder.build();
}
/** /**
* Get the initial level of the island. Used to zero island levels * Get the initial level of the island. Used to zero island levels
*
* @param island - island * @param island - island
* @return initial level of island * @return initial level of island
*/ */
@ -213,43 +338,22 @@ public class LevelsManager {
/** /**
* Get level of island from cache for a player. * Get level of island from cache for a player.
*
* @param world - world where the island is * @param world - world where the island is
* @param targetPlayer - target player UUID * @param targetPlayer - target player UUID
* @return Level of the player's island or zero if player is unknown or UUID is * @return Level of the player's island or zero if player is unknown or UUID is null
* null
*/ */
public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) { public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) if (targetPlayer == null) return 0L;
return 0L;
// Get the island // Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer); Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getLevel(); return island == null ? 0L : getLevelsData(island).getLevel();
} }
/**
* Get the maximum level ever given to this island
*
* @param world - world where the island is
* @param targetPlayer - target player UUID
* @return Max level of the player's island or zero if player is unknown or UUID
* is null
*/
public long getIslandMaxLevel(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null)
return 0L;
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getMaxLevel();
}
/** /**
* Returns a formatted string of the target player's island level * Returns a formatted string of the target player's island level
*
* @param world - world where the island is * @param world - world where the island is
* @param targetPlayer - target player's UUID * @param targetPlayer - target player's UUID
* @return Formatted level of player or zero if player is unknown or UUID is * @return Formatted level of player or zero if player is unknown or UUID is null
* null
*/ */
public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) {
return formatLevel(getIslandLevel(world, targetPlayer)); return formatLevel(getIslandLevel(world, targetPlayer));
@ -257,7 +361,6 @@ public class LevelsManager {
/** /**
* Load a level data for the island from the cache or database. * Load a level data for the island from the cache or database.
*
* @param island - UUID of island * @param island - UUID of island
* @return IslandLevels object * @return IslandLevels object
*/ */
@ -284,178 +387,112 @@ public class LevelsManager {
} }
/** /**
* Get the number of points required until the next level since the last level * Get the number of points required until the next level since the last level calc
* calc
*
* @param world - world where the island is * @param world - world where the island is
* @param targetPlayer - target player UUID * @param targetPlayer - target player UUID
* @return string with the number required or blank if the player is unknown * @return string with the number required or blank if the player is unknown
*/ */
public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) { public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) if (targetPlayer == null) return "";
return "";
Island island = addon.getIslands().getIsland(world, targetPlayer); Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel()); return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel());
} }
/** /**
* Get the weighted top ten for this world. Weighting is based on number of * Get the top ten for this world. Returns offline players or players with the intopten permission.
* players per team.
*
* @param world - world requested * @param world - world requested
* @param size - size of the top ten * @param size - size of the top ten
* @return sorted top ten map. The key is the island unique ID * @return sorted top ten map
*/ */
@NonNull @NonNull
public Map<Island, Long> getWeightedTopTen(@NonNull World world, int size) { public Map<UUID, Long> getTopTen(@NonNull World world, int size) {
createAndCleanRankings(world);
Map<Island, Long> weightedTopTen = topTenLists.get(world).getTopTen().entrySet().stream()
.map(en -> addon.getIslands().getIslandById(en.getKey()).map(island -> {
long value = (long) (en.getValue() / (double) Math.max(1, island.getMemberSet().size())); // Calculate
// weighted
// value
return new AbstractMap.SimpleEntry<>(island, value);
}).orElse(null)) // Handle islands that do not exist according to this ID - old deleted ones
.filter(Objects::nonNull) // Filter out null entries
.filter(en -> en.getValue() > 0) // Filter out entries with non-positive values
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) // Sort in descending order of values
.limit(size) // Limit to the top 'size' entries
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, // In case of key
// collision, choose
// the first one
LinkedHashMap::new // Preserves the order of entries
));
// Return the unmodifiable map
return Collections.unmodifiableMap(weightedTopTen);
}
/**
* Get the top ten for this world. Returns offline players or players with the
* intopten permission.
*
* @param world - world requested
* @param size - size of the top ten
* @return sorted top ten map. The key is the island unique ID
*/
@NonNull
public Map<String, Long> getTopTen(@NonNull World world, int size) {
createAndCleanRankings(world);
CachedData cachedData = cache.get(world);
Instant now = Instant.now();
if (cachedData != null && cachedData.getLastUpdated().plusSeconds(1).isAfter(now)) {
return cachedData.getCachedMap();
} else {
Map<String, Long> newTopTen = calculateTopTen(world, size);
cache.put(world, new CachedData(newTopTen, now));
return newTopTen;
}
}
private Map<String, Long> calculateTopTen(@NonNull World world, int size) {
return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream()
.filter(l -> l.getValue() > 0).sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.limit(size)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)));
}
void createAndCleanRankings(@NonNull World world) {
topTenLists.computeIfAbsent(world, TopTenData::new); topTenLists.computeIfAbsent(world, TopTenData::new);
// Remove player from top ten if they are online and do not have the perm // Remove player from top ten if they are online and do not have the perm
topTenLists.get(world).getTopTen().keySet().removeIf(u -> addon.getIslands().getIslandById(u) topTenLists.get(world).getTopTen().keySet().removeIf(u -> !hasTopTenPerm(world, u));
.filter(i -> i.getOwner() == null || !hasTopTenPerm(world, i.getOwner())).isPresent()); // Return the sorted map
} return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream()
.filter(e -> addon.getIslands().isOwner(world, e.getKey()))
/** .filter(l -> l.getValue() > 0)
* @return the topTenLists .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(size)
*/ .collect(Collectors.toMap(
public Map<World, TopTenData> getTopTenLists() { Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)));
return topTenLists;
}
/**
* Get the rank of the player in the rankings
*
* @param world - world
* @param uuid - player UUID
* @return rank placing - note - placing of 1 means top ranked
*/
public int getRank(@NonNull World world, UUID uuid) {
createAndCleanRankings(world);
Stream<Entry<String, Long>> stream = topTenLists.get(world).getTopTen().entrySet().stream()
.filter(l -> l.getValue() > 0).sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
// Get player's current island
Island island = addon.getIslands().getIsland(world, uuid);
String id = island == null ? null : island.getUniqueId();
return (int) (stream.takeWhile(x -> !x.getKey().equals(id)).map(Map.Entry::getKey).count() + 1);
} }
/** /**
* Checks if player has the correct top ten perm to have their level saved * Checks if player has the correct top ten perm to have their level saved
*
* @param world * @param world
* @param targetPlayer * @param targetPlayer
* @return true if player has the perm or the player is offline * @return true if player has the perm or the player is offline
*/ */
boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) { boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) {
String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world); String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world);
return Bukkit.getPlayer(targetPlayer) == null return Bukkit.getPlayer(targetPlayer) == null || Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
|| Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
} }
/** /**
* Loads all the top tens from the database * Loads all the top tens from the database
*/ */
public void loadTopTens() { void loadTopTens() {
topTenLists.clear(); topTenLists.clear();
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
addon.log("Generating rankings"); addon.log("Generating Top Ten Tables");
handler.loadObjects().forEach(il -> { handler.loadObjects().forEach(il -> {
if (il.getLevel() > 0) { if (il.getLevel() > 0) {
// Load islands, but don't cache them addon.getIslands().getIslandById(il.getUniqueId()).ifPresent(i -> this.addToTopTen(i, il.getLevel()));
addon.getIslands().getIslandById(il.getUniqueId(), false)
.ifPresent(i -> this.addToTopTen(i, il.getLevel()));
} }
}); });
topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName())); topTenLists.keySet().forEach(w -> {
addon.log("Loaded top ten for " + w.getName());
this.saveTopTen(w);
});
}); });
} }
/** /**
* Removes an island from a world's top ten * Removes a player from a world's top ten and removes world from player's level data
*
* @param world - world * @param world - world
* @param uuid - the island's uuid * @param uuid - the player's uuid
*/ */
public void removeEntry(World world, String uuid) { public void removeEntry(World world, UUID uuid) {
if (topTenLists.containsKey(world)) { if (topTenLists.containsKey(world)) {
topTenLists.get(world).getTopTen().remove(uuid); topTenLists.get(world).getTopTen().remove(uuid);
// Invalidate the cache because of this deletion topTenHandler.saveObjectAsync(topTenLists.get(world));
cache.remove(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 * Set an initial island level
*
* @param island - the island to set. Must have a non-null world * @param island - the island to set. Must have a non-null world
* @param lv - initial island level * @param lv - initial island level
*/ */
public void setInitialIslandLevel(@NonNull Island island, long lv) { public void setInitialIslandLevel(@NonNull Island island, long lv) {
if (island.getWorld() == null) if (island.getWorld() == null) return;
return;
levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv); levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv);
handler.saveObjectAsync(levelsCache.get(island.getUniqueId())); handler.saveObjectAsync(levelsCache.get(island.getUniqueId()));
} }
/** /**
* Set the island level for the owner of the island that targetPlayer is a * Set the island level for the owner of the island that targetPlayer is a member
* member
*
* @param world - world * @param world - world
* @param island - island * @param targetPlayer - player, may be a team member
* @param lv - level * @param lv - level
*/ */
public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) { public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
@ -472,36 +509,34 @@ public class LevelsManager {
} }
handler.saveObjectAsync(levelsCache.get(id)); handler.saveObjectAsync(levelsCache.get(id));
// Update TopTen // Update TopTen
addToTopTen(island, levelsCache.get(id).getLevel()); addToTopTen(world, targetPlayer, levelsCache.get(id).getLevel());
} }
} }
/** /**
* Set the island level for the owner of the island that targetPlayer is a * Set the island level for the owner of the island that targetPlayer is a member
* member
*
* @param world - world * @param world - world
* @param owner - owner of the island * @param owner - owner of the island
* @param r - results of the calculation * @param r - results of the calculation
*/ */
private void setIslandResults(Island island, Results r) { private void setIslandResults(World world, @NonNull UUID owner, Results r) {
if (island == null) // Get the island
return; Island island = addon.getIslands().getIsland(world, owner);
if (island == null) return;
IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new); IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new);
ld.setLevel(r.getLevel()); ld.setLevel(r.getLevel());
ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem))); ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem)));
ld.setMdCount(Maps.asMap(r.getMdCount().elementSet(), elem -> r.getMdCount().count(elem))); ld.setMdCount(Maps.asMap(r.getMdCount().elementSet(), elem -> r.getMdCount().count(elem)));
ld.setPointsToNextLevel(r.getPointsToNextLevel()); ld.setPointsToNextLevel(r.getPointsToNextLevel());
ld.setTotalPoints(r.getTotalPoints());
levelsCache.put(island.getUniqueId(), ld); levelsCache.put(island.getUniqueId(), ld);
handler.saveObjectAsync(ld); handler.saveObjectAsync(ld);
// Update TopTen // Update TopTen
addToTopTen(island, ld.getLevel()); addToTopTen(world, owner, ld.getLevel());
} }
/** /**
* Removes island from cache when it is deleted * Removes island from cache when it is deleted
*
* @param uniqueId - id of island * @param uniqueId - id of island
*/ */
public void deleteIsland(String uniqueId) { public void deleteIsland(String uniqueId) {

View File

@ -1,215 +0,0 @@
package world.bentobox.level;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.World;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.PlaceholdersManager;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.objects.TopTenData;
/**
* Handles Level placeholders
*
* @author tastybento
*
*/
public class PlaceholderManager {
private final Level addon;
private final BentoBox plugin;
public PlaceholderManager(Level addon) {
this.addon = addon;
this.plugin = addon.getPlugin();
}
protected void registerPlaceholders(GameModeAddon gm) {
if (plugin.getPlaceholdersManager() == null)
return;
PlaceholdersManager bpm = plugin.getPlaceholdersManager();
// Island Level
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_level",
user -> addon.getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId()));
// Unformatted island level
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_level_raw",
user -> String.valueOf(addon.getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId())));
// Total number of points counted before applying level formula
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_total_points", user -> {
IslandLevels data = addon.getManager().getLevelsData(addon.getIslands().getIsland(gm.getOverWorld(), user));
return data.getTotalPoints() + "";
});
// Points to the next level for player
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_points_to_next_level",
user -> addon.getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId()));
// Maximum level this island has ever been. Current level maybe lower.
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_level_max",
user -> String.valueOf(addon.getManager().getIslandMaxLevel(gm.getOverWorld(), user.getUniqueId())));
// Visited Island Level
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_visited_island_level",
user -> getVisitedIslandLevel(gm, user));
// Register Top Ten Placeholders
for (int i = 1; i < 11; i++) {
final int rank = i;
// Name
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_name_" + i,
u -> getRankName(gm.getOverWorld(), rank, false));
// Island Name
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i,
u -> getRankIslandName(gm.getOverWorld(), rank, false));
// Members
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_members_" + i,
u -> getRankMembers(gm.getOverWorld(), rank, false));
// Level
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_value_" + i,
u -> getRankLevel(gm.getOverWorld(), rank, false));
// Weighted Level Name (Level / number of members)
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_weighted_name_" + i,
u -> getRankName(gm.getOverWorld(), rank, true));
// Weighted Island Name
bpm.registerPlaceholder(addon,
gm.getDescription().getName().toLowerCase() + "_top_weighted_island_name_" + i,
u -> getRankIslandName(gm.getOverWorld(), rank, true));
// Weighted Members
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_weighted_members_" + i,
u -> getRankMembers(gm.getOverWorld(), rank, true));
// Weighted Level (Level / number of members)
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_weighted_value_" + i,
u -> getRankLevel(gm.getOverWorld(), rank, true));
}
// Personal rank
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_rank_value",
u -> getRankValue(gm.getOverWorld(), u));
}
/**
* Get the name of the owner of the island who holds the rank in this world.
*
* @param world world
* @param rank rank 1 to 10
* @param weighted if true, then the weighted rank name is returned
* @return rank name
*/
String getRankName(World world, int rank, boolean weighted) {
// Ensure rank is within bounds
rank = Math.max(1, Math.min(rank, Level.TEN));
if (weighted) {
return addon.getManager().getWeightedTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L)
.findFirst().map(Island::getOwner).map(addon.getPlayers()::getName).orElse("");
}
@Nullable
UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L)
.findFirst().flatMap(addon.getIslands()::getIslandById).map(Island::getOwner).orElse(null);
return addon.getPlayers().getName(owner);
}
/**
* Get the island name for this rank
*
* @param world world
* @param rank rank 1 to 10
* @param weighted if true, then the weighted rank name is returned
* @return name of island or nothing if there isn't one
*/
String getRankIslandName(World world, int rank, boolean weighted) {
// Ensure rank is within bounds
rank = Math.max(1, Math.min(rank, Level.TEN));
if (weighted) {
return addon.getManager().getWeightedTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L)
.findFirst().map(Island::getName).orElse("");
}
return addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst()
.flatMap(addon.getIslands()::getIslandById).map(Island::getName).orElse("");
}
/**
* Gets a comma separated string of island member names
*
* @param world world
* @param rank rank to request
* @param weighted if true, then the weighted rank name is returned
* @return comma separated string of island member names
*/
String getRankMembers(World world, int rank, boolean weighted) {
// Ensure rank is within bounds
rank = Math.max(1, Math.min(rank, Level.TEN));
if (weighted) {
return addon.getManager().getWeightedTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L)
.findFirst()
.map(is -> is.getMembers().entrySet().stream().filter(e -> e.getValue() >= RanksManager.MEMBER_RANK)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).map(Map.Entry::getKey)
.map(addon.getPlayers()::getName).collect(Collectors.joining(",")))
.orElse("");
}
Optional<Island> island = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L)
.limit(1L).findFirst().flatMap(addon.getIslands()::getIslandById);
if (island.isPresent()) {
// Sort members by rank
return island.get().getMembers().entrySet().stream().filter(e -> e.getValue() >= RanksManager.MEMBER_RANK)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).map(Map.Entry::getKey)
.map(addon.getPlayers()::getName).collect(Collectors.joining(","));
}
return "";
}
/**
* Get the level for the rank requested
*
* @param world world
* @param rank rank wanted
* @param weighted true if weighted (level/number of team members)
* @return level for the rank requested
*/
String getRankLevel(World world, int rank, boolean weighted) {
// Ensure rank is within bounds
rank = Math.max(1, Math.min(rank, Level.TEN));
if (weighted) {
return addon.getManager().formatLevel(addon.getManager().getWeightedTopTen(world, Level.TEN).values()
.stream().skip(rank - 1L).limit(1L).findFirst().orElse(null));
}
return addon.getManager().formatLevel(addon.getManager().getTopTen(world, Level.TEN).values().stream()
.skip(rank - 1L).limit(1L).findFirst().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.
*/
private String getRankValue(World world, User user) {
if (user == null) {
return "";
}
// Get the island level for this user
long level = addon.getManager().getIslandLevel(world, user.getUniqueId());
return String.valueOf(addon.getManager().getTopTenLists().getOrDefault(world, new TopTenData(world)).getTopTen()
.values().stream().filter(l -> l > level).count() + 1);
}
String getVisitedIslandLevel(GameModeAddon gm, User user) {
if (user == null || !gm.inWorld(user.getWorld()))
return "";
return addon.getIslands().getIslandAt(user.getLocation())
.map(island -> addon.getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner()))
.orElse("0");
}
}

View File

@ -1,121 +0,0 @@
package world.bentobox.level.calculators;
import java.text.ParseException;
/**
* Utility class to evaluate equations
*/
public class EquationEvaluator {
private static class Parser {
private final String input;
private int pos = -1;
private int currentChar;
@SuppressWarnings("unused")
private Parser() {
throw new IllegalStateException("Utility class");
}
public Parser(String input) {
this.input = input;
moveToNextChar();
}
private void moveToNextChar() {
currentChar = (++pos < input.length()) ? input.charAt(pos) : -1;
}
private boolean tryToEat(int charToEat) {
while (currentChar == ' ') {
moveToNextChar();
}
if (currentChar == charToEat) {
moveToNextChar();
return true;
}
return false;
}
public double evaluate() throws ParseException {
double result = parseExpression();
if (pos < input.length()) {
throw new ParseException("Unexpected character: " + (char) currentChar, pos);
}
return result;
}
private double parseExpression() throws ParseException {
double result = parseTerm();
while (true) {
if (tryToEat('+')) {
result += parseTerm();
} else if (tryToEat('-')) {
result -= parseTerm();
} else {
return result;
}
}
}
private double parseFactor() throws ParseException {
if (tryToEat('+')) {
return parseFactor(); // unary plus
}
if (tryToEat('-')) {
return -parseFactor(); // unary minus
}
double x;
int startPos = this.pos;
if (tryToEat('(')) { // parentheses
x = parseExpression();
tryToEat(')');
} else if ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') { // numbers
while ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') {
moveToNextChar();
}
x = Double.parseDouble(input.substring(startPos, this.pos));
} else if (currentChar >= 'a' && currentChar <= 'z') { // functions
while (currentChar >= 'a' && currentChar <= 'z') {
moveToNextChar();
}
String func = input.substring(startPos, this.pos);
x = parseFactor();
x = switch (func) {
case "sqrt" -> Math.sqrt(x);
case "sin" -> Math.sin(Math.toRadians(x));
case "cos" -> Math.cos(Math.toRadians(x));
case "tan" -> Math.tan(Math.toRadians(x));
case "log" -> Math.log(x);
default -> throw new ParseException("Unknown function: " + func, startPos);
};
} else {
throw new ParseException("Unexpected: " + (char) currentChar, startPos);
}
if (tryToEat('^')) {
x = Math.pow(x, parseFactor()); // exponentiation
}
return x;
}
private double parseTerm() throws ParseException {
double x = parseFactor();
for (;;) {
if (tryToEat('*'))
x *= parseFactor(); // multiplication
else if (tryToEat('/'))
x /= parseFactor(); // division
else
return x;
}
}
}
public static double eval(final String equation) throws ParseException {
return new Parser(equation).evaluate();
}
}

View File

@ -1,17 +1,11 @@
package world.bentobox.level.calculators; package world.bentobox.level.calculators;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.HashMap;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
@ -19,7 +13,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot; import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Tag; import org.bukkit.Tag;
import org.bukkit.World; import org.bukkit.World;
@ -27,13 +20,12 @@ import org.bukkit.World.Environment;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.Container; import org.bukkit.block.Container;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.ShulkerBox;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab; import org.bukkit.block.data.type.Slab;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta; import org.eclipse.jdt.annotation.NonNull;
import org.bukkit.scheduler.BukkitTask; import org.eclipse.jdt.annotation.Nullable;
import com.bgsoftware.wildstacker.api.WildStackerAPI; import com.bgsoftware.wildstacker.api.WildStackerAPI;
import com.bgsoftware.wildstacker.api.objects.StackedBarrel; import com.bgsoftware.wildstacker.api.objects.StackedBarrel;
@ -41,51 +33,127 @@ import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry; import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets; 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;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair; import world.bentobox.bentobox.util.Pair;
import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.Util;
import world.bentobox.level.Level; import world.bentobox.level.Level;
import world.bentobox.level.calculators.Results.Result;
public class IslandLevelCalculator { public class IslandLevelCalculator {
private static final String LINE_BREAK = "=================================="; private static final String LINE_BREAK = "==================================";
public static final long MAX_AMOUNT = 10000000; public static final long MAX_AMOUNT = 10000;
private static final List<Material> CHESTS = Arrays.asList(Material.CHEST, Material.CHEST_MINECART,
Material.TRAPPED_CHEST, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX, /**
Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, * Method to evaluate a mathematical equation
Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX, * @param str - equation to evaluate
Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX, * @return value of equation
Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.RED_SHULKER_BOX, */
Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL, private static double eval(final String str) {
Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE); return new Object() {
private static final int CHUNKS_TO_SCAN = 100; int pos = -1, ch;
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
void nextChar() {
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
return x;
}
// Grammar:
// expression = term | expression `+` term | expression `-` term
// term = factor | term `*` factor | term `/` factor
// factor = `+` factor | `-` factor | `(` expression `)`
// | number | functionName factor | factor `^` factor
double parseExpression() {
double x = parseTerm();
for (;;) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(str.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = str.substring(startPos, this.pos);
x = parseFactor();
switch (func) {
case "sqrt":
x = Math.sqrt(x);
break;
case "sin":
x = Math.sin(Math.toRadians(x));
break;
case "cos":
x = Math.cos(Math.toRadians(x));
break;
case "tan":
x = Math.tan(Math.toRadians(x));
break;
default:
throw new RuntimeException("Unknown function: " + func);
}
} else {
throw new RuntimeException("Unexpected: " + (char)ch);
}
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
double parseTerm() {
double x = parseFactor();
for (;;) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) x /= parseFactor(); // division
else return x;
}
}
}.parse();
}
private final Level addon; private final Level addon;
private final Queue<Pair<Integer, Integer>> chunksToCheck; private final Queue<Pair<Integer, Integer>> chunksToCheck;
private final Island island; private final Island island;
private final Map<Material, Integer> limitCount; private final HashMap<Material, Integer> limitCount;
private final CompletableFuture<Results> r; private final CompletableFuture<Results> r;
private final Results results; private final Results results;
private long duration; private long duration;
private final boolean zeroIsland; private final boolean zeroIsland;
private final Map<Environment, World> worlds = new EnumMap<>(Environment.class);
private final int seaHeight;
private final List<Location> stackedBlocks = new ArrayList<>();
private final Set<Chunk> chestBlocks = new HashSet<>();
private BukkitTask finishTask;
/** /**
* Constructor to get the level for an island * Constructor to get the level for an island
*
* @param addon - Level addon * @param addon - Level addon
* @param island - the island to scan * @param island - the island to scan
* @param r - completable result that will be completed when the * @param r - completable result that will be completed when the calculation is complete
* calculation is complete
* @param zeroIsland - true if the calculation is due to an island zeroing * @param zeroIsland - true if the calculation is due to an island zeroing
*/ */
public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r, boolean zeroIsland) { public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r, boolean zeroIsland) {
@ -96,54 +164,24 @@ public class IslandLevelCalculator {
results = new Results(); results = new Results();
duration = System.currentTimeMillis(); duration = System.currentTimeMillis();
chunksToCheck = getChunksToScan(island); chunksToCheck = getChunksToScan(island);
this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); this.limitCount = new HashMap<>(addon.getBlockConfig().getBlockLimits());
// Get the initial island level // Get the initial island level
results.initialLevel.set(addon.getInitialIslandLevel(island)); 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());
} }
/** /**
* Calculate the level based on the raw points * Calculate the level based on the raw points
*
* @param blockAndDeathPoints - raw points counted on island * @param blockAndDeathPoints - raw points counted on island
* @return level of island * @return level of island
*/ */
private long calculateLevel(long blockAndDeathPoints) { private long calculateLevel(long blockAndDeathPoints) {
String calcString = addon.getSettings().getLevelCalc(); String calcString = addon.getSettings().getLevelCalc();
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String.valueOf(this.addon.getSettings().getLevelCost()));
String.valueOf(this.addon.getSettings().getLevelCost())); return (long)eval(withValues) - this.island.getLevelHandicap() - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0);
long evalWithValues;
try {
evalWithValues = (long) EquationEvaluator.eval(withValues);
return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0);
} catch (ParseException e) {
addon.getPlugin().logStacktrace(e);
return 0L;
}
} }
/** /**
* Adds value to the results based on the material and whether the block is * Adds value to the results based on the material and whether the block is below sea level or not
* below sea level or not
*
* @param mat - material of the block * @param mat - material of the block
* @param belowSeaLevel - true if below sea level * @param belowSeaLevel - true if below sea level
*/ */
@ -160,22 +198,20 @@ public class IslandLevelCalculator {
/** /**
* Get a set of all the chunks in island * Get a set of all the chunks in island
*
* @param island - island * @param island - island
* @return - set of pairs of x,z coordinates to check * @return - set of pairs of x,z coordinates to check
*/ */
private Queue<Pair<Integer, Integer>> getChunksToScan(Island island) { private Queue<Pair<Integer, Integer>> getChunksToScan(Island island) {
Queue<Pair<Integer, Integer>> chunkQueue = new ConcurrentLinkedQueue<>(); Queue<Pair<Integer, Integer>> chunkQueue = new ConcurrentLinkedQueue<>();
for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) {
+ 16); x += 16) { for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) {
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2
+ 16); z += 16) {
chunkQueue.add(new Pair<>(x >> 4, z >> 4)); chunkQueue.add(new Pair<>(x >> 4, z >> 4));
} }
} }
return chunkQueue; return chunkQueue;
} }
/** /**
* @return the island * @return the island
*/ */
@ -185,7 +221,6 @@ public class IslandLevelCalculator {
/** /**
* Get the completable result for this calculation * Get the completable result for this calculation
*
* @return the r * @return the r
*/ */
public CompletableFuture<Results> getR() { public CompletableFuture<Results> getR() {
@ -194,14 +229,12 @@ public class IslandLevelCalculator {
/** /**
* Get the full analysis report * Get the full analysis report
*
* @return a list of lines * @return a list of lines
*/ */
private List<String> getReport() { private List<String> getReport() {
List<String> reportLines = new ArrayList<>(); List<String> reportLines = new ArrayList<>();
// provide counts // provide counts
reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) + " at " + Util.xyz(island.getCenter().toVector()));
+ " at " + Util.xyz(island.getCenter().toVector()));
reportLines.add("Island owner UUID = " + island.getOwner()); reportLines.add("Island owner UUID = " + island.getOwner());
reportLines.add("Total block value count = " + String.format("%,d",results.rawBlockCount.get())); reportLines.add("Total block value count = " + String.format("%,d",results.rawBlockCount.get()));
reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc()); reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc());
@ -215,8 +248,7 @@ public class IslandLevelCalculator {
reportLines.add(LINE_BREAK); reportLines.add(LINE_BREAK);
int total = 0; int total = 0;
if (!results.uwCount.isEmpty()) { if (!results.uwCount.isEmpty()) {
reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() + ") value");
+ ") value");
reportLines.add("Total number of underwater blocks = " + String.format("%,d",results.uwCount.size())); reportLines.add("Total number of underwater blocks = " + String.format("%,d",results.uwCount.size()));
reportLines.addAll(sortedReport(total, results.uwCount)); reportLines.addAll(sortedReport(total, results.uwCount));
} }
@ -224,8 +256,7 @@ public class IslandLevelCalculator {
reportLines.add("Total number of blocks = " + String.format("%,d",results.mdCount.size())); reportLines.add("Total number of blocks = " + String.format("%,d",results.mdCount.size()));
reportLines.addAll(sortedReport(total, results.mdCount)); reportLines.addAll(sortedReport(total, results.mdCount));
reportLines.add( reportLines.add("Blocks not counted because they exceeded limits: " + String.format("%,d",results.ofCount.size()));
"Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size()));
Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet(); Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet();
Iterator<Entry<Material>> it = entriesSortedByCount.iterator(); Iterator<Entry<Material>> it = entriesSortedByCount.iterator();
while (it.hasNext()) { while (it.hasNext()) {
@ -237,8 +268,7 @@ public class IslandLevelCalculator {
limit = addon.getBlockConfig().getBlockLimits().get(generic); limit = addon.getBlockConfig().getBlockLimits().get(generic);
explain = " - All types)"; explain = " - All types)";
} }
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks (max " + limit + explain);
+ " blocks (max " + limit + explain);
} }
reportLines.add(LINE_BREAK); reportLines.add(LINE_BREAK);
reportLines.add("Blocks on island that are not in config.yml"); reportLines.add("Blocks on island that are not in config.yml");
@ -260,10 +290,9 @@ public class IslandLevelCalculator {
public Results getResults() { public Results getResults() {
return results; return results;
} }
/** /**
* Get value of a material World blocks trump regular block values * Get value of a material
* * World blocks trump regular block values
* @param md - Material to check * @param md - Material to check
* @return value of a material * @return value of a material
*/ */
@ -279,57 +308,40 @@ public class IslandLevelCalculator {
/** /**
* Get a chunk async * Get a chunk async
* * @param world - the world where the chunk is
* @param env - the environment * @param env - the environment
* @param pairList - chunk coordinate * @param x - chunk x coordinate
* @return a future chunk or future null if there is no chunk to load, e.g., * @param z - chunk z coordinate
* there is no island nether * @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether
*/ */
private CompletableFuture<List<Chunk>> getWorldChunk(Environment env, Queue<Pair<Integer, Integer>> pairList) { private CompletableFuture<Chunk> getWorldChunk(@NonNull World world, Environment env, int x, int z) {
if (worlds.containsKey(env)) { switch (env) {
CompletableFuture<List<Chunk>> r2 = new CompletableFuture<>(); case NETHER:
List<Chunk> chunkList = new ArrayList<>(); if (addon.getSettings().isNether()) {
World world = worlds.get(env); World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
// Get the chunk, and then coincidentally check the RoseStacker if (nether != null) {
loadChunks(r2, world, pairList, chunkList); return Util.getChunkAtAsync(nether, x, z, true);
return r2;
} }
return CompletableFuture.completedFuture(Collections.emptyList());
} }
// 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 loadChunks(CompletableFuture<List<Chunk>> r2, World world, Queue<Pair<Integer, Integer>> pairList,
List<Chunk> chunkList) {
if (pairList.isEmpty()) {
r2.complete(chunkList);
return;
}
Pair<Integer, Integer> p = pairList.poll();
Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> {
if (chunk != null) {
chunkList.add(chunk);
roseStackerCheck(chunk);
}
loadChunks(r2, world, pairList, chunkList); // Iteration
});
}
private void roseStackerCheck(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);
}
});
} }
} }
/** /**
* Checks if a block has been limited or not and whether a block has any value * Checks if a block has been limited or not and whether a block has any value or not
* or not
*
* @param md Material * @param md Material
* @return value of the block if can be counted * @return value of the block if can be counted
*/ */
@ -347,142 +359,102 @@ public class IslandLevelCalculator {
return getValue(md); return getValue(md);
} }
/**
* Count the blocks on the island
* @param result - the CompletableFuture that should be completed when this scan is done
* @param chunkSnapshot - the chunk to scan
*/
private void scanAsync(CompletableFuture<Boolean> result, ChunkSnapshot chunkSnapshot, Chunk chunk) {
for (int x = 0; x< 16; x++) {
// Check if the block coordinate is inside the protection zone and if not, don't count it
if (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) {
continue;
}
for (int z = 0; z < 16; z++) {
// Check if the block coordinate is inside the protection zone and if not, don't count it
if (chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || chunkSnapshot.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
continue;
}
// Only count to the highest block in the world for some optimization
for (int y = 0; y < chunk.getWorld().getMaxHeight(); y++) {
BlockData blockData = chunkSnapshot.getBlockData(x, y, z);
int seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld());
boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight;
// Slabs can be doubled, so check them twice
if (Tag.SLABS.isTagged(blockData.getMaterial())) {
Slab slab = (Slab)blockData;
if (slab.getType().equals(Slab.Type.DOUBLE)) {
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
// Hook for Wild Stackers (Blocks Only) - this has to use the real chunk
if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) {
Block cauldronBlock = chunk.getBlock(x, y, z);
if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) {
StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock);
int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock);
for (int _x = 0; _x < barrelAmt; _x++) {
checkBlock(barrel.getType(), belowSeaLevel);
}
}
}
// Add the value of the block's material
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
}
// Chunk finished
if (chunk.getWorld().getEnvironment().equals(Environment.NORMAL) && chunksToCheck.isEmpty()) {
// This was the last chunk
tidyUp();
}
// Complete the future - this must go back onto the primary thread to exit async otherwise subsequent actions will be async
Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true));
}
/** /**
* Scan all containers in a chunk and count their blocks * Scan all containers in a chunk and count their blocks
*
* @param chunk - the chunk to scan * @param chunk - the chunk to scan
*/ */
private void scanChests(Chunk chunk) { private void scanChests(Chunk chunk) {
// Count blocks in chests // Count blocks in chests
for (BlockState bs : chunk.getTileEntities()) { for (BlockState bs : chunk.getTileEntities()) {
if (bs instanceof Container container) { if (bs instanceof Container) {
if (addon.isAdvChestEnabled()) { Inventory inv = ((Container)bs).getSnapshotInventory();
AdvancedChest<?, ?> aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation()); for (ItemStack i : inv) {
if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) { if (i != null && i.getType().isBlock()) {
aChest.getPages().stream().map(ChestPage::getItems).forEach(c -> {
for (Object i : c) {
countItemStack((ItemStack) i);
}
});
continue;
}
}
// Regular chest
container.getSnapshotInventory().forEach(this::countItemStack);
}
}
}
private void countItemStack(ItemStack i) {
if (i == null || !i.getType().isBlock())
return;
for (int c = 0; c < i.getAmount(); c++) { for (int c = 0; c < i.getAmount(); c++) {
if (addon.getSettings().isIncludeShulkersInChest()
&& i.getItemMeta() instanceof BlockStateMeta blockStateMeta
&& blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox) {
shulkerBox.getSnapshotInventory().forEach(this::countItemStack);
}
checkBlock(i.getType(), false); checkBlock(i.getType(), false);
} }
} }
}
}
}
}
/** /**
* Scan the chunk chests and count the blocks. Note that the chunks are a list * Scan the chunk chests and count the blocks
* of all the island chunks in a particular world, so the memory usage is high, * @param chunk - the chunk to scan
* but I think most servers can handle it. * @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not
*
* @param chunks - a list of chunks to scan
* @return future that completes when the scan is done and supplies a boolean
* that will be true if the scan was successful, false if not
*/ */
private CompletableFuture<Boolean> scanChunk(List<Chunk> chunks) { private CompletableFuture<Boolean> scanChunk(@Nullable Chunk chunk) {
// If the chunk hasn't been generated, return // If the chunk hasn't been generated, return
if (chunks == null || chunks.isEmpty()) { if (chunk == null) return CompletableFuture.completedFuture(false);
return CompletableFuture.completedFuture(false); // Scan chests
if (addon.getSettings().isIncludeChests()) {
scanChests(chunk);
} }
// Count blocks in chunk // Count blocks in chunk
CompletableFuture<Boolean> result = new CompletableFuture<>(); CompletableFuture<Boolean> result = new CompletableFuture<>();
/* ChunkSnapshot snapshot = chunk.getChunkSnapshot();
* At this point, we need to grab a snapshot of each chunk and then scan it Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> scanAsync(result, snapshot, chunk));
* async. At the end, we make the CompletableFuture true to show it is done. I'm
* not sure how much lag this will cause, but as all the chunks are loaded,
* maybe not that much.
*/
List<ChunkPair> preLoad = chunks.stream().map(c -> new ChunkPair(c.getWorld(), c, c.getChunkSnapshot()))
.toList();
Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> {
preLoad.forEach(this::scanAsync);
// Once they are all done, return to the main thread.
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> result.complete(true));
});
return result; return result;
} }
record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
}
/**
* Count the blocks on the island
*
* @param cp chunk to scan
*/
private void scanAsync(ChunkPair cp) {
for (int x = 0; x < 16; x++) {
// Check if the block coordinate is inside the protection zone and if not, don't
// count it
if (cp.chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || cp.chunkSnapshot.getX() * 16
+ x >= island.getMinProtectedX() + island.getProtectionRange() * 2) {
continue;
}
for (int z = 0; z < 16; z++) {
// Check if the block coordinate is inside the protection zone and if not, don't
// count it
if (cp.chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || cp.chunkSnapshot.getZ() * 16
+ z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
continue;
}
// Only count to the highest block in the world for some optimization
for (int y = cp.world.getMinHeight(); y < cp.world.getMaxHeight(); y++) {
BlockData blockData = cp.chunkSnapshot.getBlockData(x, y, z);
Material m = blockData.getMaterial();
boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight;
// Slabs can be doubled, so check them twice
if (Tag.SLABS.isTagged(m)) {
Slab slab = (Slab) blockData;
if (slab.getType().equals(Slab.Type.DOUBLE)) {
checkBlock(m, belowSeaLevel);
}
}
// Hook for Wild Stackers (Blocks and Spawners Only) - this has to use the real
// chunk
if (addon.isStackersEnabled() && (m.equals(Material.CAULDRON) || m.equals(Material.SPAWNER))) {
stackedBlocks.add(new Location(cp.world, (double) x + cp.chunkSnapshot.getX() * 16, y,
(double) z + cp.chunkSnapshot.getZ() * 16));
}
if (addon.isUltimateStackerEnabled() && !m.isAir()) {
Location l = new Location(cp.chunk.getWorld(), x, y, z);
UltimateStackerCalc.addStackers(m, l, results, belowSeaLevel, limitCount(m));
}
// Scan chests
if (addon.getSettings().isIncludeChests() && CHESTS.contains(m)) {
chestBlocks.add(cp.chunk);
}
// Add the value of the block's material
checkBlock(m, belowSeaLevel);
}
}
}
}
/** /**
* Scan the next chunk on the island * Scan the next chunk on the island
* * @return completable boolean future that will be true if more chunks are left to be scanned, and false if not
* @return completable boolean future that will be true if more chunks are left
* to be scanned, and false if not
*/ */
public CompletableFuture<Boolean> scanNextChunk() { public CompletableFuture<Boolean> scanNextChunk() {
if (chunksToCheck.isEmpty()) { if (chunksToCheck.isEmpty()) {
@ -491,70 +463,67 @@ public class IslandLevelCalculator {
return CompletableFuture.completedFuture(false); return CompletableFuture.completedFuture(false);
} }
// Retrieve and remove from the queue // Retrieve and remove from the queue
Queue<Pair<Integer, Integer>> pairList = new ConcurrentLinkedQueue<>(); Pair<Integer, Integer> p = chunksToCheck.poll();
int i = 0;
while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) {
pairList.add(chunksToCheck.poll());
}
Queue<Pair<Integer, Integer>> endPairList = new ConcurrentLinkedQueue<>(pairList);
Queue<Pair<Integer, Integer>> netherPairList = new ConcurrentLinkedQueue<>(pairList);
// Set up the result // Set up the result
CompletableFuture<Boolean> result = new CompletableFuture<>(); CompletableFuture<Boolean> result = new CompletableFuture<>();
// Get chunks and scan // Get chunks and scan
// Get chunks and scan getWorldChunk(island.getWorld(), Environment.THE_END, p.x, p.z).thenAccept(endChunk ->
getWorldChunk(Environment.THE_END, endPairList).thenAccept( scanChunk(endChunk).thenAccept(b ->
endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList) getWorldChunk(island.getWorld(), Environment.NETHER, p.x, p.z).thenAccept(netherChunk ->
.thenAccept(netherChunks -> scanChunk(netherChunks) scanChunk(netherChunk).thenAccept(b2 ->
.thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList) getWorldChunk(island.getWorld(), Environment.NORMAL, p.x, p.z).thenAccept(normalChunk ->
.thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 -> scanChunk(normalChunk).thenAccept(b3 ->
// Complete the result now that all chunks have been scanned // Complete the result now that all chunks have been scanned
result.complete(!chunksToCheck.isEmpty()))))))); result.complete(!chunksToCheck.isEmpty()))))
)
)
);
return result; return result;
} }
private Collection<String> sortedReport(int total, Multiset<Material> materialCount) { private Collection<String> sortedReport(int total, Multiset<Material> materialCount) {
Collection<String> result = new ArrayList<>(); Collection<String> r = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount) Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount).entrySet();
.entrySet();
for (Entry<Material> en : entriesSortedByCount) { for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement(); Material type = en.getElement();
int value = getValue(type); int value = getValue(type);
result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = " r.add(type.toString() + ":"
+ (value * en.getCount())); + String.format("%,d", en.getCount()) + " blocks x " + value + " = " + (value * en.getCount()));
total += (value * en.getCount()); total += (value * en.getCount());
} }
result.add("Subtotal = " + total); r.add("Subtotal = " + total);
result.add(LINE_BREAK); r.add(LINE_BREAK);
return result; return r;
} }
/**
* Finalizes the calculations and makes the report private void tidyUp() {
*/
public void tidyUp() {
// Finalize calculations // Finalize calculations
results.rawBlockCount results.rawBlockCount.addAndGet((long)(results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
.addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
// Set the death penalty // Set the death penalty
if (this.addon.getSettings().isSumTeamDeaths()) { if (this.addon.getSettings().isSumTeamDeaths())
for (UUID uuid : this.island.getMemberSet()) { {
for (UUID uuid : this.island.getMemberSet())
{
this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid)); this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid));
} }
} else { }
else
{
// At this point, it may be that the island has become unowned. // At this point, it may be that the island has become unowned.
this.results.deathHandicap.set(this.island.getOwner() == null ? 0 this.results.deathHandicap.set(this.island.getOwner() == null ? 0 :
: this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner())); this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner()));
} }
long blockAndDeathPoints = this.results.rawBlockCount.get(); long blockAndDeathPoints = this.results.rawBlockCount.get();
this.results.totalPoints.set(blockAndDeathPoints);
if (this.addon.getSettings().getDeathPenalty() > 0) { if (this.addon.getSettings().getDeathPenalty() > 0)
{
// Proper death penalty calculation. // Proper death penalty calculation.
blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty(); blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
} }
@ -581,79 +550,4 @@ public class IslandLevelCalculator {
boolean isNotZeroIsland() { boolean isNotZeroIsland() {
return !zeroIsland; return !zeroIsland;
} }
public void scanIsland(Pipeliner pipeliner) {
// Scan the next chunk
scanNextChunk().thenAccept(result -> {
if (!Bukkit.isPrimaryThread()) {
addon.getPlugin().logError("scanChunk not on Primary Thread!");
}
// Timeout check
if (System.currentTimeMillis()
- pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) {
// Done
pipeliner.getInProcessQueue().remove(this);
getR().complete(new Results(Result.TIMEOUT));
addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout()
+ "m for island: " + getIsland());
if (!isNotZeroIsland()) {
addon.logError("Island level was being zeroed.");
}
return;
}
if (Boolean.TRUE.equals(result) && !pipeliner.getTask().isCancelled()) {
// scanNextChunk returns true if there are more chunks to scan
scanIsland(pipeliner);
} else {
// Done
pipeliner.getInProcessQueue().remove(this);
// Chunk finished
// This was the last chunk. Handle stacked blocks, then chests and exit
handleStackedBlocks().thenCompose(v -> handleChests()).thenRun(() -> {
this.tidyUp();
this.getR().complete(getResults());
});
}
});
}
private CompletableFuture<Void> handleChests() {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (Chunk v : chestBlocks) {
CompletableFuture<Void> future = Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> {
scanChests(c);
});
futures.add(future);
}
// Return a CompletableFuture that completes when all futures are done
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Void> handleStackedBlocks() {
// Deal with any stacked blocks
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (Location v : stackedBlocks) {
CompletableFuture<Void> future = Util.getChunkAtAsync(v).thenAccept(c -> {
Block stackedBlock = v.getBlock();
boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight;
if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) {
StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock);
int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock);
for (int _x = 0; _x < barrelAmt; _x++) {
checkBlock(barrel.getType(), belowSeaLevel);
}
} else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) {
int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState());
for (int _x = 0; _x < spawnerAmt; _x++) {
checkBlock(stackedBlock.getType(), belowSeaLevel);
}
}
});
futures.add(future);
}
// Return a CompletableFuture that completes when all futures are done
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
} }

View File

@ -50,7 +50,7 @@ public class Pipeliner {
if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) { if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) {
inProcessQueue.put(iD, System.currentTimeMillis()); inProcessQueue.put(iD, System.currentTimeMillis());
// Start the scanning of a island with the first chunk // Start the scanning of a island with the first chunk
scanIsland(iD); scanChunk(iD);
} }
} }
}, 1L, 10L); }, 1L, 10L);
@ -71,14 +71,39 @@ public class Pipeliner {
* Scans one chunk of an island and adds the results to a results object * Scans one chunk of an island and adds the results to a results object
* @param iD * @param iD
*/ */
private void scanIsland(IslandLevelCalculator iD) { private void scanChunk(IslandLevelCalculator iD) {
if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) { if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) {
// Island is deleted, so finish early with nothing // Island is deleted, so finish early with nothing
inProcessQueue.remove(iD); inProcessQueue.remove(iD);
iD.getR().complete(null); iD.getR().complete(null);
return; return;
} }
iD.scanIsland(this); // Scan the next chunk
iD.scanNextChunk().thenAccept(r -> {
if (!Bukkit.isPrimaryThread()) {
addon.getPlugin().logError("scanChunk not on Primary Thread!");
}
// Timeout check
if (System.currentTimeMillis() - inProcessQueue.get(iD) > addon.getSettings().getCalculationTimeout() * 60000) {
// Done
inProcessQueue.remove(iD);
iD.getR().complete(new Results(Result.TIMEOUT));
addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() + "m for island: " + iD.getIsland());
if (!iD.isNotZeroIsland()) {
addon.logError("Island level was being zeroed.");
}
return;
}
if (Boolean.TRUE.equals(r) || task.isCancelled()) {
// scanNextChunk returns true if there are more chunks to scan
scanChunk(iD);
} else {
// Done
inProcessQueue.remove(iD);
iD.getR().complete(iD.getResults());
}
});
} }
@ -141,20 +166,6 @@ public class Pipeliner {
this.toProcessQueue.clear(); this.toProcessQueue.clear();
} }
/**
* @return the inProcessQueue
*/
protected Map<IslandLevelCalculator, Long> getInProcessQueue() {
return inProcessQueue;
}
/**
* @return the task
*/
protected BukkitTask getTask() {
return task;
}

View File

@ -36,7 +36,6 @@ public class Results {
AtomicInteger deathHandicap = new AtomicInteger(0); AtomicInteger deathHandicap = new AtomicInteger(0);
AtomicLong pointsToNextLevel = new AtomicLong(0); AtomicLong pointsToNextLevel = new AtomicLong(0);
AtomicLong initialLevel = new AtomicLong(0); AtomicLong initialLevel = new AtomicLong(0);
AtomicLong totalPoints = new AtomicLong(0);
final Result state; final Result state;
public Results(Result state) { public Results(Result state) {
@ -94,21 +93,6 @@ public class Results {
pointsToNextLevel.set(points); pointsToNextLevel.set(points);
} }
/**
* @return the totalPoints
*/
public long getTotalPoints() {
return totalPoints.get();
}
/**
* Set the total points
* @param points
*/
public void setTotalPoints(long points) {
totalPoints.set(points);
}
public long getInitialLevel() { public long getInitialLevel() {
return initialLevel.get(); return initialLevel.get();
} }
@ -125,7 +109,7 @@ public class Results {
return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + uwCount + ", ncCount=" return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + uwCount + ", ncCount="
+ ncCount + ", ofCount=" + ofCount + ", rawBlockCount=" + rawBlockCount + ", underWaterBlockCount=" + ncCount + ", ofCount=" + ofCount + ", rawBlockCount=" + rawBlockCount + ", underWaterBlockCount="
+ underWaterBlockCount + ", level=" + level + ", deathHandicap=" + deathHandicap + underWaterBlockCount + ", level=" + level + ", deathHandicap=" + deathHandicap
+ ", pointsToNextLevel=" + pointsToNextLevel + ", totalPoints=" + totalPoints + ", initialLevel=" + initialLevel + "]"; + ", pointsToNextLevel=" + pointsToNextLevel + ", initialLevel=" + initialLevel + "]";
} }
/** /**
* @return the mdCount * @return the mdCount

View File

@ -1,29 +0,0 @@
package world.bentobox.level.calculators;
import org.bukkit.Location;
import org.bukkit.Material;
import com.craftaro.ultimatestacker.api.UltimateStackerApi;
import com.craftaro.ultimatestacker.api.utils.Stackable;
import world.bentobox.bentobox.BentoBox;
/**
* Isolates UltimateStacker imports so that they are only loaded if the plugin exists
*/
public class UltimateStackerCalc {
public static void addStackers(Material material, Location location, Results results, boolean belowSeaLevel,
int value) {
Stackable stack = UltimateStackerApi.getBlockStackManager().getBlock(location);
if (stack != null) {
if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet((long) stack.getAmount() * value);
results.uwCount.add(material);
} else {
results.rawBlockCount.addAndGet((long) stack.getAmount() * value);
results.mdCount.add(material);
}
}
}
}

View File

@ -1,98 +0,0 @@
package world.bentobox.level.commands;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.bukkit.World;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level;
import world.bentobox.level.objects.TopTenData;
public class AdminStatsCommand extends CompositeCommand {
private final Level level;
public AdminStatsCommand(Level addon, CompositeCommand parent) {
super(parent, "stats");
this.level = addon;
new AdminTopRemoveCommand(addon, this);
}
@Override
public void setup() {
this.setPermission("admin.stats");
this.setOnlyPlayer(false);
this.setDescription("admin.stats.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
user.sendMessage("admin.stats.title");
Map<World, TopTenData> topTenLists = level.getManager().getTopTenLists();
if (topTenLists.isEmpty()) {
user.sendMessage("admin.stats.no-data");
return false;
}
for (Entry<World, TopTenData> en : topTenLists.entrySet()) {
user.sendMessage("admin.stats.world", TextVariables.NAME,
level.getPlugin().getIWM().getWorldName(en.getKey()));
Map<String, Long> topTen = en.getValue().getTopTen();
if (topTen.isEmpty()) {
user.sendMessage("admin.stats.no-data");
return false;
}
// Calculating basic statistics
long sum = 0, max = Long.MIN_VALUE, min = Long.MAX_VALUE;
Map<Long, Integer> levelFrequency = new HashMap<>();
for (Long level : topTen.values()) {
sum += level;
max = Math.max(max, level);
min = Math.min(min, level);
levelFrequency.merge(level, 1, Integer::sum);
}
double average = sum / (double) topTen.size();
List<Long> sortedLevels = topTen.values().stream().sorted().collect(Collectors.toList());
long median = sortedLevels.get(sortedLevels.size() / 2);
Long mode = Collections.max(levelFrequency.entrySet(), Map.Entry.comparingByValue()).getKey();
// Logging basic statistics
user.sendMessage("admin.stats.average-level", TextVariables.NUMBER, String.valueOf(average));
user.sendMessage("admin.stats.median-level", TextVariables.NUMBER, String.valueOf(median));
user.sendMessage("admin.stats.mode-level", TextVariables.NUMBER, String.valueOf(mode));
user.sendMessage("admin.stats.highest-level", TextVariables.NUMBER, String.valueOf(max));
user.sendMessage("admin.stats.lowest-level", TextVariables.NUMBER, String.valueOf(min));
// Grouping data for distribution analysis
Map<String, Integer> rangeMap = new TreeMap<>();
for (Long level : topTen.values()) {
String range = getRange(level);
rangeMap.merge(range, 1, Integer::sum);
}
// Logging distribution
user.sendMessage("admin.stats.distribution");
for (Map.Entry<String, Integer> entry : rangeMap.entrySet()) {
user.sendMessage(
entry.getKey() + ": " + entry.getValue() + " " + user.getTranslation("admin.stats.islands"));
}
}
return true;
}
private static String getRange(long level) {
long rangeStart = level / 100 * 100;
long rangeEnd = rangeStart + 99;
return rangeStart + "-" + rangeEnd;
}
}

View File

@ -2,7 +2,7 @@ package world.bentobox.level.commands;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
@ -30,16 +30,20 @@ public class AdminTopCommand extends CompositeCommand {
public boolean execute(User user, String label, List<String> args) { public boolean execute(User user, String label, List<String> args) {
user.sendMessage("island.top.gui-title"); user.sendMessage("island.top.gui-title");
int rank = 0; int rank = 0;
for (Map.Entry<String, Long> topTen : levelPlugin.getManager().getTopTen(getWorld(), Level.TEN).entrySet()) { for (Map.Entry<UUID, Long> topTen : levelPlugin.getManager().getTopTen(getWorld(), 10).entrySet()) {
Optional<Island> is = getPlugin().getIslands().getIslandById(topTen.getKey()); Island island = getPlugin().getIslands().getIsland(getWorld(), topTen.getKey());
if (is.isPresent()) { if (island != null) {
Island island = is.get();
rank++; rank++;
user.sendMessage("admin.top.display", "[rank]", String.valueOf(rank), "[name]", user.sendMessage("admin.top.display",
this.getPlugin().getPlayers().getUser(island.getOwner()).getName(), "[level]", "[rank]",
String.valueOf(rank),
"[name]",
this.getPlugin().getPlayers().getUser(island.getOwner()).getName(),
"[level]",
String.valueOf(topTen.getValue())); String.valueOf(topTen.getValue()));
} }
} }
return true; return true;
} }
} }

View File

@ -2,16 +2,15 @@ package world.bentobox.level.commands;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.Level; import world.bentobox.level.Level;
/** /**
* Removes a player from the top ten * Removes a player from the top ten
*
* @author tastybento * @author tastybento
* *
*/ */
@ -33,11 +32,8 @@ public class AdminTopRemoveCommand extends CompositeCommand {
this.setDescription("admin.top.remove.description"); this.setDescription("admin.top.remove.description");
} }
/* /* (non-Javadoc)
* (non-Javadoc) * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)
*
* @see world.bentobox.bentobox.api.commands.BentoBoxCommand#canExecute(world.
* bentobox.bentobox.api.user.User, java.lang.String, java.util.List)
*/ */
@Override @Override
public boolean canExecute(User user, String label, List<String> args) { public boolean canExecute(User user, String label, List<String> args) {
@ -56,18 +52,14 @@ public class AdminTopRemoveCommand extends CompositeCommand {
@Override @Override
public boolean execute(User user, String label, List<String> args) { public boolean execute(User user, String label, List<String> args) {
// Removes islands that this target is an owner of addon.getManager().removeEntry(getWorld(), target.getUniqueId());
getIslands().getIslands(getWorld(), target.getUniqueId()).stream()
.filter(is -> target.getUniqueId().equals(is.getOwner()))
.forEach(island -> addon.getManager().removeEntry(getWorld(), island.getUniqueId()));
user.sendMessage("general.success"); user.sendMessage("general.success");
return true; return true;
} }
@Override @Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) { public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
return Optional.of(addon.getManager().getTopTen(getWorld(), Level.TEN).keySet().stream() return Optional.of(addon.getManager().getTopTen(getWorld(), 10).keySet().stream().map(addon.getPlayers()::getName)
.map(getIslands()::getIslandById).flatMap(Optional::stream).map(Island::getOwner) .filter(n -> !n.isEmpty()).collect(Collectors.toList()));
.map(addon.getPlayers()::getName).filter(n -> !n.isEmpty()).toList());
} }
} }

View File

@ -110,12 +110,8 @@ public class IslandLevelCommand extends CompositeCommand {
user.sendMessage("island.level.deaths", "[number]", String.valueOf(results.getDeathHandicap())); user.sendMessage("island.level.deaths", "[number]", String.valueOf(results.getDeathHandicap()));
} }
// Send player how many points are required to reach next island level // Send player how many points are required to reach next island level
if (results.getPointsToNextLevel() >= 0) { if (results.getPointsToNextLevel() >= 0 && results.getPointsToNextLevel() < 10000) {
user.sendMessage("island.level.required-points-to-next-level", user.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel()));
"[points]", String.valueOf(results.getPointsToNextLevel()),
"[progress]", String.valueOf(this.addon.getSettings().getLevelCost()-results.getPointsToNextLevel()),
"[levelcost]", String.valueOf(this.addon.getSettings().getLevelCost())
);
} }
// Tell other team members // Tell other team members
if (results.getLevel() != oldLevel) { if (results.getLevel() != oldLevel) {
@ -123,7 +119,7 @@ public class IslandLevelCommand extends CompositeCommand {
.filter(u -> !u.equals(user.getUniqueId())) .filter(u -> !u.equals(user.getUniqueId()))
.forEach(m -> User.getInstance(m).sendMessage(ISLAND_LEVEL_IS, LEVEL, addon.getManager().getIslandLevelString(getWorld(), playerUUID))); .forEach(m -> User.getInstance(m).sendMessage(ISLAND_LEVEL_IS, LEVEL, addon.getManager().getIslandLevelString(getWorld(), playerUUID)));
} }
} else if (this.addon.getSettings().isLogReportToConsole()) { } else {
results.getReport().forEach(BentoBox.getInstance()::log); results.getReport().forEach(BentoBox.getInstance()::log);
} }

View File

@ -5,8 +5,6 @@ import java.util.List;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level; import world.bentobox.level.Level;
import world.bentobox.level.panels.TopLevelPanel;
public class IslandTopCommand extends CompositeCommand { public class IslandTopCommand extends CompositeCommand {
@ -26,7 +24,7 @@ public class IslandTopCommand extends CompositeCommand {
@Override @Override
public boolean execute(User user, String label, List<String> list) { public boolean execute(User user, String label, List<String> list) {
TopLevelPanel.openPanel(this.addon, user, this.getWorld(), this.getPermissionPrefix()); addon.getManager().getGUI(getWorld(), user);
return true; return true;
} }
} }

View File

@ -1,9 +1,6 @@
package world.bentobox.level.commands; package world.bentobox.level.commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -11,133 +8,43 @@ import org.bukkit.inventory.PlayerInventory;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.Level; import world.bentobox.level.Level;
import world.bentobox.level.panels.ValuePanel;
import world.bentobox.level.util.Utils;
public class IslandValueCommand extends CompositeCommand {
public class IslandValueCommand extends CompositeCommand
{
private static final String MATERIAL = "[material]";
private final Level addon; private final Level addon;
public IslandValueCommand(Level addon, CompositeCommand parent) {
public IslandValueCommand(Level addon, CompositeCommand parent)
{
super(parent, "value"); super(parent, "value");
this.addon = addon; this.addon = addon;
} }
@Override @Override
public void setup() public void setup() {
{
this.setPermission("island.value"); this.setPermission("island.value");
this.setParametersHelp("level.commands.value.parameters"); this.setDescription("island.value.description");
this.setDescription("level.commands.value.description");
this.setOnlyPlayer(true); this.setOnlyPlayer(true);
} }
@Override @Override
public boolean execute(User user, String label, List<String> args) public boolean execute(User user, String label, List<String> args) {
{
if (args.size() > 1)
{
this.showHelp(this, user);
return false;
}
if (args.isEmpty())
{
ValuePanel.openPanel(this.addon, this.getWorld(), user);
}
else if (args.get(0).equalsIgnoreCase("HAND"))
{
Player player = user.getPlayer(); Player player = user.getPlayer();
PlayerInventory inventory = player.getInventory(); PlayerInventory inventory = player.getInventory();
if (!inventory.getItemInMainHand().getType().equals(Material.AIR)) {
if (!inventory.getItemInMainHand().getType().equals(Material.AIR)) Material material = inventory.getItemInMainHand().getType();
{ Integer value = addon.getBlockConfig().getValue(getWorld(), material);
this.printValue(user, inventory.getItemInMainHand().getType()); if (value != null) {
user.sendMessage("island.value.success", "[value]", String.valueOf(value));
double underWater = addon.getSettings().getUnderWaterMultiplier();
if (underWater > 1.0) {
user.sendMessage("island.value.success-underwater", "[value]", (underWater * value) + "");
} }
else } else {
{ user.sendMessage("island.value.no-value");
Utils.sendMessage(user, user.getTranslation("level.conversations.empty-hand"));
} }
} else {
user.sendMessage("island.value.empty-hand");
} }
else
{
Material material = Material.matchMaterial(args.get(0));
if (material == null)
{
Utils.sendMessage(user,
user.getTranslation(this.getWorld(), "level.conversations.unknown-item",
MATERIAL, args.get(0)));
}
else
{
this.printValue(user, material);
}
}
return true; return true;
} }
/**
* This method prints value of the given material in chat.
* @param user User who receives the message.
* @param material Material value.
*/
private void printValue(User user, Material material)
{
Integer value = this.addon.getBlockConfig().getValue(getWorld(), material);
if (value != null)
{
Utils.sendMessage(user,
user.getTranslation(this.getWorld(), "level.conversations.value",
"[value]", String.valueOf(value),
MATERIAL, Utils.prettifyObject(material, user)));
double underWater = this.addon.getSettings().getUnderWaterMultiplier();
if (underWater > 1.0)
{
Utils.sendMessage(user,
user.getTranslation(this.getWorld(),"level.conversations.success-underwater",
"[value]", (underWater * value) + ""),
MATERIAL, Utils.prettifyObject(material, user));
}
}
else
{
Utils.sendMessage(user,
user.getTranslation(this.getWorld(),"level.conversations.no-value"));
}
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args)
{
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
if (args.isEmpty())
{
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Arrays.stream(Material.values()).
filter(Material::isBlock).
map(Material::name).toList());
options.add("HAND");
return Optional.of(Util.tabLimit(options, lastArg));
}
} }

View File

@ -60,16 +60,10 @@ public class BlockConfig {
if (bWorld != null) { if (bWorld != null) {
ConfigurationSection worldValues = worlds.getConfigurationSection(world); ConfigurationSection worldValues = worlds.getConfigurationSection(world);
for (String material : Objects.requireNonNull(worldValues).getKeys(false)) { for (String material : Objects.requireNonNull(worldValues).getKeys(false)) {
try {
Material mat = Material.valueOf(material); Material mat = Material.valueOf(material);
Map<Material, Integer> values = worldBlockValues.getOrDefault(bWorld, Map<Material, Integer> values = worldBlockValues.getOrDefault(bWorld, new EnumMap<>(Material.class));
new EnumMap<>(Material.class));
values.put(mat, worldValues.getInt(material)); values.put(mat, worldValues.getInt(material));
worldBlockValues.put(bWorld, values); worldBlockValues.put(bWorld, values);
} catch (Exception e) {
addon.logError(
"Unknown material (" + material + ") in blockconfig.yml worlds section. Skipping...");
}
} }
} else { } else {
addon.logWarning("Level Addon: No such world in blockconfig.yml : " + world); addon.logWarning("Level Addon: No such world in blockconfig.yml : " + world);
@ -103,7 +97,7 @@ public class BlockConfig {
Material mat = Material.valueOf(material); Material mat = Material.valueOf(material);
bl.put(mat, limits.getInt(material, 0)); bl.put(mat, limits.getInt(material, 0));
} catch (Exception e) { } catch (Exception e) {
addon.logError("Unknown material (" + material + ") in blockconfig.yml Limits section. Skipping..."); addon.logWarning("Unknown material (" + material + ") in blockconfig.yml Limits section. Skipping...");
} }
} }
return bl; return bl;

View File

@ -1,6 +1,5 @@
package world.bentobox.level.config; package world.bentobox.level.config;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -20,11 +19,6 @@ public class ConfigSettings implements ConfigObject {
@ConfigEntry(path = "disabled-game-modes") @ConfigEntry(path = "disabled-game-modes")
private List<String> gameModes = Collections.emptyList(); private List<String> gameModes = Collections.emptyList();
@ConfigComment("")
@ConfigComment("When executing level command from console, should a report be shown?")
@ConfigEntry(path = "log-report-to-console")
private boolean logReportToConsole = true;
@ConfigComment("") @ConfigComment("")
@ConfigComment("Number of concurrent island calculations") @ConfigComment("Number of concurrent island calculations")
@ConfigComment("If your CPU can handle it, you can run parallel island calcs if there are more than one in the queue") @ConfigComment("If your CPU can handle it, you can run parallel island calcs if there are more than one in the queue")
@ -91,7 +85,7 @@ public class ConfigSettings implements ConfigObject {
@ConfigComment("Island level calculation formula") @ConfigComment("Island level calculation formula")
@ConfigComment("blocks - the sum total of all block values, less any death penalty") @ConfigComment("blocks - the sum total of all block values, less any death penalty")
@ConfigComment("level_cost - in a linear equation, the value of one level") @ConfigComment("level_cost - in a linear equation, the value of one level")
@ConfigComment("This formula can include +,=,*,/,sqrt,^,sin,cos,tan,log (natural log). Result will always be rounded to a long integer") @ConfigComment("This formula can include +,=,*,/,sqrt,^,sin,cos,tan. Result will always be rounded to a long integer")
@ConfigComment("for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost)") @ConfigComment("for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost)")
@ConfigEntry(path = "level-calc") @ConfigEntry(path = "level-calc")
private String levelCalc = "blocks / level_cost"; private String levelCalc = "blocks / level_cost";
@ -120,18 +114,6 @@ public class ConfigSettings implements ConfigObject {
@ConfigComment("Shows large level values rounded down, e.g., 10,345 -> 10k") @ConfigComment("Shows large level values rounded down, e.g., 10,345 -> 10k")
@ConfigEntry(path = "shorthand") @ConfigEntry(path = "shorthand")
private boolean shorthand = false; private boolean shorthand = false;
@ConfigComment("")
@ConfigComment("Include Shulker Box content in chests in level calculations.")
@ConfigComment("Will count blocks in Shulker Boxes inside of chests.")
@ConfigComment("NOTE: include-chests needs to be enabled for this to work!.")
@ConfigEntry(path = "include-shulkers-in-chest")
private boolean includeShulkersInChest = false;
@ConfigComment("")
@ConfigComment("Disables hooking with other plugins.")
@ConfigComment("Example: disabled-plugin-hooks: [UltimateStacker, RoseStacker]")
@ConfigEntry(path = "disabled-plugin-hooks")
private List<String> disabledPluginHooks = new ArrayList<>();
/** /**
@ -382,41 +364,4 @@ public class ConfigSettings implements ConfigObject {
this.calculationTimeout = calculationTimeout; this.calculationTimeout = calculationTimeout;
} }
/**
* @return logReportToConsole
*/
public boolean isLogReportToConsole() {
return logReportToConsole;
}
/**
* @param logReportToConsole if logReportToConsole should be shown on console
*/
public void setLogReportToConsole(boolean logReportToConsole) {
this.logReportToConsole = logReportToConsole;
}
/**
* @return includeShulkersInChest
*/
public boolean isIncludeShulkersInChest() {
return includeShulkersInChest;
}
/**
* @param includeShulkersInChest the includeChests to set
*/
public void setIncludeShulkersInChest(boolean includeShulkersInChest) {
this.includeShulkersInChest = includeShulkersInChest;
}
public List<String> getDisabledPluginHooks() {
return disabledPluginHooks;
}
public void setDisabledPluginHooks(List<String> disabledPluginHooks) {
this.disabledPluginHooks = disabledPluginHooks;
}
} }

View File

@ -3,9 +3,6 @@ package world.bentobox.level.events;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.bukkit.event.HandlerList;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.calculators.Results; import world.bentobox.level.calculators.Results;
@ -21,17 +18,6 @@ public class IslandLevelCalculatedEvent extends IslandBaseEvent {
private UUID targetPlayer; private UUID targetPlayer;
private static final HandlerList handlers = new HandlerList();
@Override
public @NonNull HandlerList getHandlers() {
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
/** /**
* @param targetPlayer - target player * @param targetPlayer - target player
* @param island - island * @param island - island

View File

@ -2,9 +2,6 @@ package world.bentobox.level.events;
import java.util.UUID; import java.util.UUID;
import org.bukkit.event.HandlerList;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
@ -16,16 +13,6 @@ import world.bentobox.bentobox.database.objects.Island;
public class IslandPreLevelEvent extends IslandBaseEvent { public class IslandPreLevelEvent extends IslandBaseEvent {
private final UUID targetPlayer; private final UUID targetPlayer;
private static final HandlerList handlers = new HandlerList();
@Override
public @NonNull HandlerList getHandlers() {
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
/** /**

View File

@ -1,27 +1,27 @@
package world.bentobox.level.listeners; package world.bentobox.level.listeners;
import java.util.UUID;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import world.bentobox.bentobox.api.events.island.IslandCreatedEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.IslandCreatedEvent;
import world.bentobox.bentobox.api.events.island.IslandDeleteEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeleteEvent;
import world.bentobox.bentobox.api.events.island.IslandPreclearEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.IslandPreclearEvent;
import world.bentobox.bentobox.api.events.island.IslandRegisteredEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.IslandRegisteredEvent;
import world.bentobox.bentobox.api.events.island.IslandResettedEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.IslandResettedEvent;
import world.bentobox.bentobox.api.events.island.IslandUnregisteredEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.IslandUnregisteredEvent;
import world.bentobox.bentobox.api.events.team.TeamJoinedEvent; import world.bentobox.bentobox.api.events.team.TeamEvent.TeamJoinedEvent;
import world.bentobox.bentobox.api.events.team.TeamKickEvent; import world.bentobox.bentobox.api.events.team.TeamEvent.TeamKickEvent;
import world.bentobox.bentobox.api.events.team.TeamLeaveEvent; import world.bentobox.bentobox.api.events.team.TeamEvent.TeamLeaveEvent;
import world.bentobox.bentobox.api.events.team.TeamSetownerEvent; import world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.Level; import world.bentobox.level.Level;
/** /**
* Listens for new islands or ownership changes and sets the level to zero * Listens for new islands or ownership changes and sets the level to zero automatically
* automatically
*
* @author tastybento * @author tastybento
* *
*/ */
@ -54,14 +54,18 @@ public class IslandActivitiesListeners implements Listener {
private void zeroIsland(final Island island) { private void zeroIsland(final Island island) {
// Clear the island setting // Clear the island setting
if (island.getOwner() != null && island.getWorld() != null) { if (island.getOwner() != null && island.getWorld() != null) {
addon.getPipeliner().zeroIsland(island) addon.getPipeliner().zeroIsland(island).thenAccept(results ->
.thenAccept(results -> addon.getManager().setInitialIslandLevel(island, results.getLevel())); addon.getManager().setInitialIslandLevel(island, results.getLevel()));
} }
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onIslandDelete(IslandPreclearEvent e) { public void onIslandDelete(IslandPreclearEvent e) {
remove(e.getIsland().getWorld(), e.getIsland().getUniqueId());
// Remove player from the top ten and level
UUID uuid = e.getIsland().getOwner();
World world = e.getIsland().getWorld();
remove(world, uuid);
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -70,7 +74,7 @@ public class IslandActivitiesListeners implements Listener {
addon.getManager().deleteIsland(e.getIsland().getUniqueId()); addon.getManager().deleteIsland(e.getIsland().getUniqueId());
} }
private void remove(World world, String uuid) { private void remove(World world, UUID uuid) {
if (uuid != null && world != null) { if (uuid != null && world != null) {
addon.getManager().removeEntry(world, uuid); addon.getManager().removeEntry(world, uuid);
} }
@ -79,43 +83,43 @@ public class IslandActivitiesListeners implements Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onNewIslandOwner(TeamSetownerEvent e) { public void onNewIslandOwner(TeamSetownerEvent e) {
// Remove island from the top ten and level // Remove player from the top ten and level
remove(e.getIsland().getWorld(), e.getIsland().getUniqueId()); remove(e.getIsland().getWorld(), e.getIsland().getOwner());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(TeamJoinedEvent e) { public void onIsland(TeamJoinedEvent e) {
// TODO: anything to do here?
// Remove player from the top ten and level // Remove player from the top ten and level
// remove(e.getIsland().getWorld(), e.getPlayerUUID()); remove(e.getIsland().getWorld(), e.getPlayerUUID());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(IslandUnregisteredEvent e) { public void onIsland(IslandUnregisteredEvent e) {
// Remove island from the top ten // Remove player from the top ten
remove(e.getIsland().getWorld(), e.getIsland().getUniqueId()); remove(e.getIsland().getWorld(), e.getPlayerUUID());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(IslandRegisteredEvent e) { public void onIsland(IslandRegisteredEvent e) {
// TODO: anything to do here?
// Remove player from the top ten // Remove player from the top ten
// remove(e.getIsland().getWorld(), e.getPlayerUUID()); remove(e.getIsland().getWorld(), e.getPlayerUUID());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(TeamLeaveEvent e) { public void onIsland(TeamLeaveEvent e) {
// TODO: anything to do here?
// Remove player from the top ten and level // Remove player from the top ten and level
// remove(e.getIsland().getWorld(), e.getPlayerUUID()); remove(e.getIsland().getWorld(), e.getPlayerUUID());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(TeamKickEvent e) { public void onIsland(TeamKickEvent e) {
//// TODO: anything to do here?
// Remove player from the top ten and level // Remove player from the top ten and level
// remove(e.getIsland().getWorld(), e.getPlayerUUID()); remove(e.getIsland().getWorld(), e.getPlayerUUID());
} }
} }

View File

@ -1,61 +0,0 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.level.listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
import world.bentobox.level.Level;
/**
* This listener checks when BentoBox is ready and then tries to migrate Levels addon database, if it is required.
*/
public class MigrationListener implements Listener
{
public MigrationListener(Level addon)
{
this.addon = addon;
}
@EventHandler
public void onBentoBoxReady(BentoBoxReadyEvent e) {
// Perform upgrade check
this.addon.getManager().migrate();
// Load TopTens
this.addon.getManager().loadTopTens();
/*
* DEBUG code to generate fake islands and then try to level them all.
Bukkit.getScheduler().runTaskLater(getPlugin(), () -> {
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
for (int i = 0; i < 1000; i++) {
try {
NewIsland.builder().addon(gm).player(User.getInstance(UUID.randomUUID())).name("default").reason(Reason.CREATE).noPaste().build();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
});
// Queue all islands DEBUG
getIslands().getIslands().stream().filter(Island::isOwned).forEach(is -> {
this.getManager().calculateLevel(is.getOwner(), is).thenAccept(r ->
log("Result for island calc " + r.getLevel() + " at " + is.getCenter()));
});
}, 60L);*/
}
private final Level addon;
}

View File

@ -43,17 +43,6 @@ public class IslandLevels implements DataObject {
*/ */
@Expose @Expose
private long pointsToNextLevel; private long pointsToNextLevel;
/**
* The maximum level this island has ever had
*/
@Expose
private long maxLevel;
/**
* Total points
*/
@Expose
private long totalPoints;
/** /**
* Underwater count * Underwater count
@ -105,10 +94,6 @@ public class IslandLevels implements DataObject {
*/ */
public void setLevel(long level) { public void setLevel(long level) {
this.level = level; this.level = level;
// Track maximum level
if (level > this.maxLevel) {
maxLevel = level;
}
} }
/** /**
@ -139,28 +124,6 @@ public class IslandLevels implements DataObject {
this.pointsToNextLevel = pointsToNextLevel; this.pointsToNextLevel = pointsToNextLevel;
} }
/**
* @return the totalPoints
*/
public long getTotalPoints() {
return totalPoints;
}
/**
* @param totalPoints the totalPoints to set
*/
public void setTotalPoints(long totalPoints) {
this.totalPoints = totalPoints;
}
/**
* Get the maximum level ever set using {@link #setLevel(long)}
* @return the maxLevel
*/
public long getMaxLevel() {
return maxLevel;
}
/** /**
* @return the uwCount * @return the uwCount
*/ */

View File

@ -58,6 +58,8 @@ public class LevelsData implements DataObject {
/** /**
* Create a level entry for target player * Create a level entry for target player
* @param targetPlayer - target player * @param targetPlayer - target player
* @param level - level
* @param world - world
*/ */
public LevelsData(UUID targetPlayer) { public LevelsData(UUID targetPlayer) {
uniqueId = targetPlayer.toString(); uniqueId = targetPlayer.toString();

View File

@ -3,41 +3,56 @@ package world.bentobox.level.objects;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import org.bukkit.World; import org.bukkit.World;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
/** /**
* This class stores the top ten. * This class stores the top ten.
*
* @author tastybento * @author tastybento
* *
*/ */
public class TopTenData { @Table(name = "TopTenData")
public class TopTenData implements DataObject {
// UniqueId is the world name // UniqueId is the world name
@Expose @Expose
private String uniqueId = ""; private String uniqueId = "";
@Expose @Expose
private Map<String, Long> topTen = new LinkedHashMap<>(); private Map<UUID, Long> topTen = new LinkedHashMap<>();
public TopTenData(World k) { public TopTenData(World k) {
uniqueId = k.getName().toLowerCase(Locale.ENGLISH); uniqueId = k.getName().toLowerCase(Locale.ENGLISH);
} }
@Override
public String getUniqueId() {
// This is the world name
return uniqueId;
}
@Override
public void setUniqueId(String uniqueId) {
// This is the world name - make it always lowercase
this.uniqueId = uniqueId.toLowerCase(Locale.ENGLISH);
}
/** /**
* @return the topTen * @return the topTen
*/ */
public Map<String, Long> getTopTen() { public Map<UUID, Long> getTopTen() {
return topTen; return topTen;
} }
/** /**
* @param topTen the topTen to set * @param topTen the topTen to set
*/ */
public void setTopTen(Map<String, Long> topTen) { public void setTopTen(Map<UUID, Long> topTen) {
this.topTen = topTen; this.topTen = topTen;
} }
} }

View File

@ -0,0 +1,211 @@
/**
*
*/
package world.bentobox.level.panels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.base.Enums;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler;
import world.bentobox.bentobox.api.panels.Tab;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.Level;
import world.bentobox.level.objects.IslandLevels;
/**
* @author tastybento
*
*/
public class DetailsGUITab implements Tab, ClickHandler {
public enum DetailsType {
ABOVE_SEA_LEVEL_BLOCKS,
ALL_BLOCKS,
SPAWNERS,
UNDERWATER_BLOCKS
}
/**
* Converts block materials to item materials
*/
private static final Map<Material, Material> M2I;
static {
Map<Material, Material> m2i = new EnumMap<>(Material.class);
m2i.put(Material.WATER, Material.WATER_BUCKET);
m2i.put(Material.LAVA, Material.LAVA_BUCKET);
m2i.put(Material.AIR, Material.BLACK_STAINED_GLASS_PANE);
m2i.put(Material.VOID_AIR, Material.BLACK_STAINED_GLASS_PANE);
m2i.put(Material.CAVE_AIR, Material.BLACK_STAINED_GLASS_PANE);
m2i.put(Material.WALL_TORCH, Material.TORCH);
m2i.put(Material.REDSTONE_WALL_TORCH, Material.REDSTONE_TORCH);
m2i.put(Material.TALL_SEAGRASS, Material.SEAGRASS);
m2i.put(Material.PISTON_HEAD, Material.PISTON);
m2i.put(Material.MOVING_PISTON, Material.PISTON);
m2i.put(Material.REDSTONE_WIRE, Material.REDSTONE);
m2i.put(Material.NETHER_PORTAL, Material.MAGENTA_STAINED_GLASS_PANE);
m2i.put(Material.END_PORTAL, Material.BLACK_STAINED_GLASS_PANE);
m2i.put(Material.ATTACHED_MELON_STEM, Material.MELON_SEEDS);
m2i.put(Material.ATTACHED_PUMPKIN_STEM, Material.PUMPKIN_SEEDS);
m2i.put(Material.MELON_STEM, Material.MELON_SEEDS);
m2i.put(Material.PUMPKIN_STEM, Material.PUMPKIN_SEEDS);
m2i.put(Material.COCOA, Material.COCOA_BEANS);
m2i.put(Material.TRIPWIRE, Material.STRING);
m2i.put(Material.CARROTS, Material.CARROT);
m2i.put(Material.POTATOES, Material.POTATO);
m2i.put(Material.BEETROOTS, Material.BEETROOT);
m2i.put(Material.END_GATEWAY, Material.BEDROCK);
m2i.put(Material.FROSTED_ICE, Material.ICE);
m2i.put(Material.KELP_PLANT, Material.KELP);
m2i.put(Material.BUBBLE_COLUMN, Material.WATER_BUCKET);
m2i.put(Material.SWEET_BERRY_BUSH, Material.SWEET_BERRIES);
m2i.put(Material.BAMBOO_SAPLING, Material.BAMBOO);
m2i.put(Material.FIRE, Material.FLINT_AND_STEEL);
// 1.16.1
if (Enums.getIfPresent(Material.class, "WEEPING_VINES_PLANT").isPresent()) {
m2i.put(Material.WEEPING_VINES_PLANT, Material.WEEPING_VINES);
m2i.put(Material.TWISTING_VINES_PLANT, Material.TWISTING_VINES);
m2i.put(Material.SOUL_WALL_TORCH, Material.SOUL_TORCH);
}
M2I = Collections.unmodifiableMap(m2i);
}
private final Level addon;
private final @Nullable Island island;
private List<PanelItem> items;
private DetailsType type;
private final User user;
private final World world;
public DetailsGUITab(Level addon, World world, User user, DetailsType type) {
this.addon = addon;
this.world = world;
this.user = user;
this.island = addon.getIslands().getIsland(world, user);
this.type = type;
// Generate report
generateReport(type);
}
private void createItem(Material m, Integer count) {
if (count == null || count <= 0) return;
// Convert walls
m = Enums.getIfPresent(Material.class, m.name().replace("WALL_", "")).or(m);
// Tags
if (Enums.getIfPresent(Material.class, "SOUL_CAMPFIRE").isPresent()) {
if (Tag.FIRE.isTagged(m)) {
items.add(new PanelItemBuilder()
.icon(Material.CAMPFIRE)
.name(Util.prettifyText(m.name()) + " x " + count)
.build());
return;
}
}
if (Tag.FLOWER_POTS.isTagged(m)) {
m = Enums.getIfPresent(Material.class, m.name().replace("POTTED_", "")).or(m);
}
items.add(new PanelItemBuilder()
.icon(M2I.getOrDefault(m, m))
.name(user.getTranslation("island.level-details.syntax", TextVariables.NAME,
Util.prettifyText(m.name()), TextVariables.NUMBER, String.valueOf(count)))
.build());
}
private void generateReport(DetailsType type) {
items = new ArrayList<>();
IslandLevels ld = addon.getManager().getLevelsData(island);
// Get the items from the report
Map<Material, Integer> sumTotal = new EnumMap<>(Material.class);
sumTotal.putAll(ld.getMdCount());
sumTotal.putAll(ld.getUwCount());
switch(type) {
case ABOVE_SEA_LEVEL_BLOCKS:
ld.getMdCount().forEach(this::createItem);
break;
case SPAWNERS:
sumTotal.entrySet().stream().filter(m -> m.getKey().equals(Material.SPAWNER)).forEach(e -> createItem(e.getKey(), e.getValue()));
break;
case UNDERWATER_BLOCKS:
ld.getUwCount().forEach(this::createItem);
break;
default:
sumTotal.forEach(this::createItem);
break;
}
if (type.equals(DetailsType.ALL_BLOCKS) && items.isEmpty()) {
// Nothing here - looks like they need to run level
items.add(new PanelItemBuilder()
.name(user.getTranslation("island.level-details.hint")).icon(Material.WRITTEN_BOOK)
.build());
}
}
@Override
public PanelItem getIcon() {
switch(type) {
case ABOVE_SEA_LEVEL_BLOCKS:
return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name(user.getTranslation("island.level-details.above-sea-level-blocks")).build();
case SPAWNERS:
return new PanelItemBuilder().icon(Material.SPAWNER).name(user.getTranslation("island.level-details.spawners")).build();
case UNDERWATER_BLOCKS:
return new PanelItemBuilder().icon(Material.WATER_BUCKET).name(user.getTranslation("island.level-details.underwater-blocks")).build();
default:
return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name(user.getTranslation("island.level-details.all-blocks")).build();
}
}
@Override
public String getName() {
String name = user.getTranslation("island.level-details.no-island");
if (island.getOwner() != null) {
name = island.getName() != null ? island.getName() :
user.getTranslation("island.level-details.names-island", TextVariables.NAME, addon.getPlayers().getName(island.getOwner()));
}
return name;
}
@Override
public List<@Nullable PanelItem> getPanelItems() {
return items;
}
@Override
public String getPermission() {
String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world);
switch(type) {
case ABOVE_SEA_LEVEL_BLOCKS:
return permPrefix + "island.level.details.above-sea-level";
case SPAWNERS:
return permPrefix + "island.level.details.spawners";
case UNDERWATER_BLOCKS:
return permPrefix + "island.level.details.underwater";
default:
return permPrefix + "island.level.details.blocks";
}
}
@Override
public boolean onClick(Panel panel, User user, ClickType clickType, int slot) {
return true;
}
}

View File

@ -1,687 +0,0 @@
package world.bentobox.level.panels;
import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import com.google.common.base.Enums;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.level.Level;
import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.util.Utils;
/**
* This class opens GUI that shows generator view for user.
*/
public class DetailsPanel {
// ---------------------------------------------------------------------
// Section: Internal Constructor
// ---------------------------------------------------------------------
/**
* This is internal constructor. It is used internally in current class to avoid
* creating objects everywhere.
*
* @param addon Level object
* @param world World where user is operating
* @param user User who opens panel
*/
private DetailsPanel(Level addon, World world, User user) {
this.addon = addon;
this.world = world;
this.user = user;
this.island = this.addon.getIslands().getIsland(world, user);
if (this.island != null) {
this.levelsData = this.addon.getManager().getLevelsData(this.island);
} else {
this.levelsData = null;
}
// By default no-filters are active.
this.activeTab = Tab.ALL_BLOCKS;
this.activeFilter = Filter.NAME;
this.materialCountList = new ArrayList<>(Material.values().length);
this.updateFilters();
}
/**
* This method builds this GUI.
*/
private void build() {
if (this.island == null || this.levelsData == null) {
// Nothing to see.
Utils.sendMessage(this.user, this.user.getTranslation("general.errors.no-island"));
return;
}
if (this.levelsData.getMdCount().isEmpty() && this.levelsData.getUwCount().isEmpty()) {
// Nothing to see.
Utils.sendMessage(this.user, this.user.getTranslation("level.conversations.no-data"));
return;
}
// Start building panel.
TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
panelBuilder.user(this.user);
panelBuilder.world(this.user.getWorld());
panelBuilder.template("detail_panel", new File(this.addon.getDataFolder(), "panels"));
panelBuilder.parameters("[name]", this.user.getName());
panelBuilder.registerTypeBuilder("NEXT", this::createNextButton);
panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton);
panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton);
// Register tabs
panelBuilder.registerTypeBuilder("TAB", this::createTabButton);
// Register unknown type builder.
panelBuilder.build();
}
/**
* This method updates filter of elements based on tabs.
*/
private void updateFilters() {
this.materialCountList.clear();
switch (this.activeTab) {
case ALL_BLOCKS -> {
Map<Material, Integer> materialCountMap = new EnumMap<>(Material.class);
materialCountMap.putAll(this.levelsData.getMdCount());
// Add underwater blocks.
this.levelsData.getUwCount().forEach((material, count) -> materialCountMap.put(material,
materialCountMap.computeIfAbsent(material, key -> 0) + count));
materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey()))
.forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue())));
}
case ABOVE_SEA_LEVEL -> this.levelsData.getMdCount().entrySet().stream().sorted((Map.Entry.comparingByKey()))
.forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue())));
case UNDERWATER -> this.levelsData.getUwCount().entrySet().stream().sorted((Map.Entry.comparingByKey()))
.forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue())));
case SPAWNER -> {
int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0);
int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0);
// TODO: spawners need some touch...
this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater));
}
}
Comparator<Pair<Material, Integer>> sorter;
switch (this.activeFilter) {
case COUNT -> {
sorter = (o1, o2) -> {
if (o1.getValue().equals(o2.getValue())) {
String o1Name = Utils.prettifyObject(o1.getKey(), this.user);
String o2Name = Utils.prettifyObject(o2.getKey(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
} else {
return Integer.compare(o2.getValue(), o1.getValue());
}
};
}
case VALUE -> {
sorter = (o1, o2) -> {
int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0);
int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue();
blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0);
int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue();
long o1Value = (long) o1Count
* this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0);
long o2Value = (long) o2Count
* this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0);
if (o1Value == o2Value) {
String o1Name = Utils.prettifyObject(o1.getKey(), this.user);
String o2Name = Utils.prettifyObject(o2.getKey(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
} else {
return Long.compare(o2Value, o1Value);
}
};
}
default -> {
sorter = (o1, o2) -> {
String o1Name = Utils.prettifyObject(o1.getKey(), this.user);
String o2Name = Utils.prettifyObject(o2.getKey(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
};
}
}
this.materialCountList.sort(sorter);
this.pageIndex = 0;
}
// ---------------------------------------------------------------------
// Section: Tab Button Type
// ---------------------------------------------------------------------
/**
* Create tab button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
// Set icon
builder.icon(template.icon().clone());
}
if (template.title() != null) {
// Set title
builder.name(this.user.getTranslation(this.world, template.title()));
}
if (template.description() != null) {
// Set description
builder.description(this.user.getTranslation(this.world, template.description()));
}
Tab tab = Enums.getIfPresent(Tab.class, String.valueOf(template.dataMap().get("tab"))).or(Tab.ALL_BLOCKS);
// Get only possible actions, by removing all inactive ones.
List<ItemTemplateRecord.ActionRecords> activeActions = new ArrayList<>(template.actions());
activeActions.removeIf(action -> "VIEW".equalsIgnoreCase(action.actionType()) && this.activeTab == tab);
// Add Click handler
builder.clickHandler((panel, user, clickType, i) -> {
for (ItemTemplateRecord.ActionRecords action : activeActions) {
if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
&& "VIEW".equalsIgnoreCase(action.actionType())) {
this.activeTab = tab;
// Update filters.
this.updateFilters();
this.build();
}
}
return true;
});
// Collect tooltips.
List<String> tooltips = activeActions.stream().filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty()) {
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
builder.glow(this.activeTab == tab);
return builder.build();
}
/**
* Create next button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
// Set icon
builder.icon(template.icon().clone());
}
Filter filter;
if (slot.amountMap().getOrDefault("FILTER", 0) > 1) {
filter = Enums.getIfPresent(Filter.class, String.valueOf(template.dataMap().get("filter"))).or(Filter.NAME);
} else {
filter = this.activeFilter;
}
final String reference = "level.gui.buttons.filters.";
if (template.title() != null) {
// Set title
builder.name(this.user.getTranslation(this.world,
template.title().replace("[filter]", filter.name().toLowerCase())));
} else {
builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".name"));
}
if (template.description() != null) {
// Set description
builder.description(this.user.getTranslation(this.world,
template.description().replace("[filter]", filter.name().toLowerCase())));
} else {
builder.name(
this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".description"));
}
// Get only possible actions, by removing all inactive ones.
List<ItemTemplateRecord.ActionRecords> activeActions = new ArrayList<>(template.actions());
// Add Click handler
builder.clickHandler((panel, user, clickType, i) -> {
for (ItemTemplateRecord.ActionRecords action : activeActions) {
if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) {
if ("UP".equalsIgnoreCase(action.actionType())) {
this.activeFilter = Utils.getNextValue(Filter.values(), filter);
// Update filters.
this.updateFilters();
this.build();
} else if ("DOWN".equalsIgnoreCase(action.actionType())) {
this.activeFilter = Utils.getPreviousValue(Filter.values(), filter);
// Update filters.
this.updateFilters();
this.build();
} else if ("SELECT".equalsIgnoreCase(action.actionType())) {
this.activeFilter = filter;
// Update filters.
this.updateFilters();
this.build();
}
}
}
return true;
});
// Collect tooltips.
List<String> tooltips = activeActions.stream().filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty()) {
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
builder.glow(this.activeFilter == filter);
return builder.build();
}
// ---------------------------------------------------------------------
// Section: Create common buttons
// ---------------------------------------------------------------------
/**
* Create next button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
long size = this.materialCountList.size();
if (size <= slot.amountMap().getOrDefault("BLOCK", 1)
|| 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) {
// There are no next elements
return null;
}
int nextPageIndex = this.pageIndex + 2;
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
ItemStack clone = template.icon().clone();
if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) {
clone.setAmount(nextPageIndex);
}
builder.icon(clone);
}
if (template.title() != null) {
builder.name(this.user.getTranslation(this.world, template.title()));
}
if (template.description() != null) {
builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER,
String.valueOf(nextPageIndex)));
}
// Add ClickHandler
builder.clickHandler((panel, user, clickType, i) -> {
for (ItemTemplateRecord.ActionRecords action : template.actions()) {
if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
&& "NEXT".equalsIgnoreCase(action.actionType())) {
this.pageIndex++;
this.build();
}
}
// Always return true.
return true;
});
// Collect tooltips.
List<String> tooltips = template.actions().stream().filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty()) {
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
return builder.build();
}
/**
* Create previous button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
if (this.pageIndex == 0) {
// There are no next elements
return null;
}
int previousPageIndex = this.pageIndex;
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
ItemStack clone = template.icon().clone();
if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) {
clone.setAmount(previousPageIndex);
}
builder.icon(clone);
}
if (template.title() != null) {
builder.name(this.user.getTranslation(this.world, template.title()));
}
if (template.description() != null) {
builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER,
String.valueOf(previousPageIndex)));
}
// Add ClickHandler
builder.clickHandler((panel, user, clickType, i) -> {
for (ItemTemplateRecord.ActionRecords action : template.actions()) {
if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
&& "PREVIOUS".equalsIgnoreCase(action.actionType())) {
this.pageIndex--;
this.build();
}
}
// Always return true.
return true;
});
// Collect tooltips.
List<String> tooltips = template.actions().stream().filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty()) {
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
return builder.build();
}
// ---------------------------------------------------------------------
// Section: Create Material Button
// ---------------------------------------------------------------------
/**
* Create material button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
if (this.materialCountList.isEmpty()) {
// Does not contain any generators.
return null;
}
int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot();
if (index >= this.materialCountList.size()) {
// Out of index.
return null;
}
return this.createMaterialButton(template, this.materialCountList.get(index));
}
/**
* This method creates button for material.
*
* @param template the template of the button
* @param materialCount materialCount which button must be created.
* @return PanelItem for generator tier.
*/
private PanelItem createMaterialButton(ItemTemplateRecord template, Pair<Material, Integer> materialCount) {
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
builder.icon(template.icon().clone());
} else {
builder.icon(PanelUtils.getMaterialItem(materialCount.getKey()));
}
if (materialCount.getValue() < 64) {
builder.amount(materialCount.getValue());
}
if (template.title() != null) {
builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NUMBER,
String.valueOf(materialCount.getValue()), "[material]",
Utils.prettifyObject(materialCount.getKey(), this.user)));
}
String description = Utils.prettifyDescription(materialCount.getKey(), this.user);
final String reference = "level.gui.buttons.material.";
String blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", materialCount.getKey().name());
int blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(materialCount.getKey(), 0);
String value = blockValue > 0
? this.user.getTranslationOrNothing(reference + "value", TextVariables.NUMBER,
String.valueOf(blockValue))
: "";
int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(materialCount.getKey(), 0);
String limit = blockLimit > 0
? this.user.getTranslationOrNothing(reference + "limit", TextVariables.NUMBER,
String.valueOf(blockLimit))
: "";
String count = this.user.getTranslationOrNothing(reference + "count", TextVariables.NUMBER,
String.valueOf(materialCount.getValue()));
long calculatedValue = (long) Math.min(blockLimit > 0 ? blockLimit : Integer.MAX_VALUE,
materialCount.getValue()) * blockValue;
String valueText = calculatedValue > 0 ? this.user.getTranslationOrNothing(reference + "calculated",
TextVariables.NUMBER, String.valueOf(calculatedValue)) : "";
if (template.description() != null) {
builder.description(this.user
.getTranslation(this.world, template.description(), "[description]", description, "[id]", blockId,
"[value]", value, "[calculated]", valueText, "[limit]", limit, "[count]", count)
.replaceAll("(?m)^[ \\t]*\\r?\\n", "").replaceAll("(?<!\\\\)\\|", "\n").replace("\\\\\\|", "|"));
}
return builder.build();
}
// ---------------------------------------------------------------------
// Section: Other Methods
// ---------------------------------------------------------------------
/**
* This method is used to open UserPanel outside this class. It will be much
* easier to open panel with single method call then initializing new object.
*
* @param addon Level object
* @param world World where user is operating
* @param user User who opens panel
*/
public static void openPanel(Level addon, World world, User user) {
new DetailsPanel(addon, world, user).build();
}
// ---------------------------------------------------------------------
// Section: Enums
// ---------------------------------------------------------------------
/**
* This enum holds possible tabs for current gui.
*/
private enum Tab {
/**
* All block Tab
*/
ALL_BLOCKS,
/**
* Above Sea level Tab.
*/
ABOVE_SEA_LEVEL,
/**
* Underwater Tab.
*/
UNDERWATER,
/**
* Spawner Tab.
*/
SPAWNER
}
/**
* Sorting order of blocks.
*/
private enum Filter {
/**
* By name
*/
NAME,
/**
* By value
*/
VALUE,
/**
* By number
*/
COUNT
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable holds targeted island.
*/
private final Island island;
/**
* This variable holds targeted island level data.
*/
private final IslandLevels levelsData;
/**
* This variable allows to access addon object.
*/
private final Level addon;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds a world to which gui referee.
*/
private final World world;
/**
* This variable stores the list of elements to display.
*/
private final List<Pair<Material, Integer>> materialCountList;
/**
* This variable holds current pageIndex for multi-page generator choosing.
*/
private int pageIndex;
/**
* This variable stores which tab currently is active.
*/
private Tab activeTab;
/**
* This variable stores active filter for items.
*/
private Filter activeFilter;
}

View File

@ -1,467 +0,0 @@
///
// Created by BONNe
// Copyright - 2021
///
package world.bentobox.level.panels;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.level.Level;
import world.bentobox.level.util.Utils;
/**
* This panel opens top likes panel
*/
public class TopLevelPanel {
// ---------------------------------------------------------------------
// Section: Internal Constructor
// ---------------------------------------------------------------------
/**
* This is internal constructor. It is used internally in current class to avoid
* creating objects everywhere.
*
* @param addon Level object.
* @param user User who opens Panel.
* @param world World where gui is opened
* @param permissionPrefix Permission Prefix
*/
private TopLevelPanel(Level addon, User user, World world, String permissionPrefix) {
this.addon = addon;
this.user = user;
this.world = world;
this.iconPermission = permissionPrefix + "level.icon";
topIslands = new ArrayList<>();
for (Map.Entry<String, Long> en : addon.getManager().getTopTen(this.world, Level.TEN).entrySet()) {
Optional<Island> is = addon.getIslands().getIslandById(en.getKey());
if (is.isPresent()) {
topIslands.add(new IslandTopRecord(is.get(), en.getValue()));
}
}
}
/**
* Build method manages current panel opening. It uses BentoBox PanelAPI that is
* easy to use and users can get nice panels.
*/
public void build() {
TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
panelBuilder.user(this.user);
panelBuilder.world(this.world);
panelBuilder.template("top_panel", new File(this.addon.getDataFolder(), "panels"));
panelBuilder.registerTypeBuilder("VIEW", this::createViewerButton);
panelBuilder.registerTypeBuilder("TOP", this::createPlayerButton);
// Register unknown type builder.
panelBuilder.build();
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* Creates fallback based on template.
*
* @param template Template record for fallback button.
* @param index Place of the fallback.
* @return Fallback panel item.
*/
private PanelItem createFallback(ItemTemplateRecord template, long index) {
if (template == null) {
return null;
}
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
builder.icon(template.icon().clone());
}
if (template.title() != null) {
builder.name(
this.user.getTranslation(this.world, template.title(), TextVariables.NAME, String.valueOf(index)));
} else {
builder.name(this.user.getTranslation(this.world, REFERENCE, TextVariables.NAME, String.valueOf(index)));
}
if (template.description() != null) {
builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER,
String.valueOf(index)));
}
builder.amount(index != 0 ? (int) index : 1);
return builder.build();
}
/**
* This method creates player icon with warp functionality.
*
* @return PanelItem for PanelBuilder.
*/
private PanelItem createPlayerButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot itemSlot) {
int index = (int) template.dataMap().getOrDefault("index", 0);
if (index < 1) {
return this.createFallback(template.fallback(), index);
}
IslandTopRecord islandTopRecord = this.topIslands.size() < index ? null : this.topIslands.get(index - 1);
if (islandTopRecord == null) {
return this.createFallback(template.fallback(), index);
}
return this.createIslandIcon(template, islandTopRecord, index);
}
/**
* This method creates button from template for given island top record.
*
* @param template Icon Template.
* @param islandTopRecord Island Top Record.
* @param index Place Index.
* @return PanelItem for PanelBuilder.
*/
private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord islandTopRecord, int index) {
// Get player island.
Island island = islandTopRecord.island();
if (island == null) {
return this.createFallback(template.fallback(), index);
}
PanelItemBuilder builder = new PanelItemBuilder();
this.populateIslandIcon(builder, template, island);
this.populateIslandTitle(builder, template, island);
this.populateIslandDescription(builder, template, island, islandTopRecord, index);
builder.amount(index);
// Get only possible actions, by removing all inactive ones.
List<ItemTemplateRecord.ActionRecords> activeActions = new ArrayList<>(template.actions());
activeActions.removeIf(action -> {
switch (action.actionType().toUpperCase()) {
case "WARP" -> {
return island.getOwner() == null || this.addon.getWarpHook() == null
|| !this.addon.getWarpHook().getWarpSignsManager().hasWarp(this.world, island.getOwner());
}
case "VISIT" -> {
return island.getOwner() == null || this.addon.getVisitHook() == null
|| !this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island, true);
}
case "VIEW" -> {
return island.getOwner() == null
|| !island.getMemberSet(RanksManager.MEMBER_RANK).contains(this.user.getUniqueId());
}
default -> {
return false;
}
}
});
// Add Click handler
builder.clickHandler((panel, user, clickType, i) -> {
for (ItemTemplateRecord.ActionRecords action : activeActions) {
if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) {
switch (action.actionType().toUpperCase()) {
case "WARP" -> {
this.user.closeInventory();
this.addon.getWarpHook().getWarpSignsManager().warpPlayer(this.world, this.user,
island.getOwner());
}
case "VISIT" ->
// The command call implementation solves necessity to check for all visits
// options,
// like cool down, confirmation and preprocess in single go. Would it be better
// to write
// all logic here?
this.addon.getPlugin().getIWM().getAddon(this.world).flatMap(GameModeAddon::getPlayerCommand)
.ifPresent(command -> {
String mainCommand = this.addon.getVisitHook().getSettings().getPlayerMainCommand();
if (!mainCommand.isBlank()) {
this.user.closeInventory();
this.user.performCommand(
command.getTopLabel() + " " + mainCommand + " " + island.getOwner());
}
});
case "VIEW" -> {
this.user.closeInventory();
// Open Detailed GUI.
DetailsPanel.openPanel(this.addon, this.world, this.user);
}
// Catch default
default -> {
this.user.closeInventory();
addon.logError("Unknown action type " + action.actionType().toUpperCase());
}
}
}
}
return true;
});
// Collect tooltips.
List<String> tooltips = activeActions.stream().filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty()) {
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
return builder.build();
}
/**
* Populate given panel item builder name with values from template and island
* objects.
*
* @param builder the builder
* @param template the template
* @param island the island
*/
private void populateIslandTitle(PanelItemBuilder builder, ItemTemplateRecord template, Island island) {
// Get Island Name
String nameText;
if (island.getName() == null || island.getName().isEmpty()) {
nameText = this.user.getTranslation(REFERENCE + "owners-island", PLAYER,
island.getOwner() == null ? this.user.getTranslation(REFERENCE + "unknown")
: this.addon.getPlayers().getName(island.getOwner()));
} else {
nameText = island.getName();
}
// Template specific title is always more important than custom one.
if (template.title() != null && !template.title().isBlank()) {
builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NAME, nameText));
} else {
builder.name(this.user.getTranslation(REFERENCE + "name", TextVariables.NAME, nameText));
}
}
/**
* Populate given panel item builder icon with values from template and island
* objects.
*
* @param builder the builder
* @param template the template
* @param island the island
*/
private void populateIslandIcon(PanelItemBuilder builder, ItemTemplateRecord template, Island island) {
User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner());
// Get permission or island icon
String permissionIcon = owner == null ? null : Utils.getPermissionValue(owner, this.iconPermission, null);
Material material;
if (permissionIcon != null && !permissionIcon.equals("*")) {
material = Material.matchMaterial(permissionIcon);
} else {
material = null;
}
if (material != null) {
if (!material.equals(Material.PLAYER_HEAD)) {
builder.icon(material);
} else {
builder.icon(owner.getName());
}
} else if (template.icon() != null) {
builder.icon(template.icon().clone());
} else if (owner != null) {
builder.icon(owner.getName());
} else {
builder.icon(Material.PLAYER_HEAD);
}
}
/**
* Populate given panel item builder description with values from template and
* island objects.
*
* @param builder the builder
* @param template the template
* @param island the island
* @param islandTopRecord the top record object
* @param index place index.
*/
private void populateIslandDescription(PanelItemBuilder builder, ItemTemplateRecord template, Island island,
IslandTopRecord islandTopRecord, int index) {
// Get Owner Name
String ownerText = this.user.getTranslation(REFERENCE + "owner", PLAYER,
island.getOwner() == null ? this.user.getTranslation(REFERENCE + "unknown")
: this.addon.getPlayers().getName(island.getOwner()));
// Get Members Text
String memberText;
if (island.getMemberSet().size() > 1) {
StringBuilder memberBuilder = new StringBuilder(
this.user.getTranslationOrNothing(REFERENCE + "members-title"));
for (UUID uuid : island.getMemberSet()) {
User member = User.getInstance(uuid);
if (memberBuilder.length() > 0) {
memberBuilder.append("\n");
}
memberBuilder.append(this.user.getTranslationOrNothing(REFERENCE + "member", PLAYER, member.getName()));
}
memberText = memberBuilder.toString();
} else {
memberText = "";
}
String placeText = this.user.getTranslation(REFERENCE + "place", TextVariables.NUMBER, String.valueOf(index));
String levelText = this.user.getTranslation(REFERENCE + "level", TextVariables.NUMBER,
this.addon.getManager().formatLevel(islandTopRecord.level()));
// Template specific description is always more important than custom one.
if (template.description() != null && !template.description().isBlank()) {
builder.description(this.user
.getTranslation(this.world, template.description(), "[owner]", ownerText, "[members]", memberText,
"[level]", levelText, "[place]", placeText)
.replaceAll("(?m)^[ \\t]*\\r?\\n", "").replaceAll("(?<!\\\\)\\|", "\n").replace("\\\\\\|", "|")); // Not
// a
// regex
// -
// replace
// is
// more
// efficient
} else {
// Now combine everything.
String descriptionText = this.user.getTranslation(REFERENCE + "description", "[owner]", ownerText,
"[members]", memberText, "[level]", levelText, "[place]", placeText);
builder.description(descriptionText.replaceAll("(?m)^[ \\t]*\\r?\\n", "").replaceAll("(?<!\\\\)\\|", "\n")
.replace("\\\\\\|", "|")); // Not a regex - replace is more efficient
}
}
/**
* Create viewer button panel item.
*
* @return PanelItem for PanelBuilder.
*/
private PanelItem createViewerButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot itemSlot) {
Island island = this.addon.getIslands().getIsland(this.world, this.user);
if (island == null || island.getOwner() == null) {
// Player do not have an island.
return null;
}
int place = this.addon.getManager().getRank(this.world, this.user.getUniqueId());
long level = this.addon.getIslandLevel(this.world, island.getOwner());
IslandTopRecord topRecord = new IslandTopRecord(island, level);
return this.createIslandIcon(template, topRecord, place);
}
/**
* This method is used to open UserPanel outside this class. It will be much
* easier to open panel with single method call then initializing new object.
*
* @param addon Level Addon object
* @param user User who opens panel
* @param world World where gui is opened
* @param permissionPrefix Permission Prefix
*/
public static void openPanel(Level addon, User user, World world, String permissionPrefix) {
new TopLevelPanel(addon, user, world, permissionPrefix).build();
}
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
private static final String REFERENCE = "level.gui.buttons.island.";
private static final String PLAYER = "[player]";
// ---------------------------------------------------------------------
// Section: Record
// ---------------------------------------------------------------------
/**
* This record is used internally. It converts user -> level to island -> level.
*
* @param island island
* @param level level
*/
private record IslandTopRecord(Island island, Long level) {
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable allows to access addon object.
*/
private final Level addon;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds a world to which gui referee.
*/
private final World world;
/**
* Location to icon permission.
*/
private final String iconPermission;
/**
* List of top 10 island records.
*/
private final List<IslandTopRecord> topIslands;
}

View File

@ -1,779 +0,0 @@
package world.bentobox.level.panels;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import com.google.common.base.Enums;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level;
import world.bentobox.level.util.ConversationUtils;
import world.bentobox.level.util.Utils;
/**
* This class opens GUI that shows generator view for user.
*/
public class ValuePanel
{
// ---------------------------------------------------------------------
// Section: Internal Constructor
// ---------------------------------------------------------------------
/**
* This is internal constructor. It is used internally in current class to avoid creating objects everywhere.
*
* @param addon Level object
* @param world World where user is operating
* @param user User who opens panel
*/
private ValuePanel(Level addon,
World world,
User user)
{
this.addon = addon;
this.world = world;
this.user = user;
this.activeFilter = Filter.NAME_ASC;
this.materialRecordList = Arrays.stream(Material.values()).
filter(Material::isBlock).
filter(m -> !m.name().startsWith("LEGACY_")).
map(material ->
{
Integer value = this.addon.getBlockConfig().getValue(this.world, material);
Integer limit = this.addon.getBlockConfig().getBlockLimits().get(material);
return new MaterialRecord(material,
value != null ? value : 0,
limit != null ? limit : 0);
}).
collect(Collectors.toList());
this.elementList = new ArrayList<>(Material.values().length);
this.searchText = "";
this.updateFilters();
}
/**
* This method builds this GUI.
*/
private void build()
{
// Start building panel.
TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
panelBuilder.user(this.user);
panelBuilder.world(this.user.getWorld());
panelBuilder.template("value_panel", new File(this.addon.getDataFolder(), "panels"));
panelBuilder.registerTypeBuilder("NEXT", this::createNextButton);
panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
panelBuilder.registerTypeBuilder(BLOCK, this::createMaterialButton);
panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton);
panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton);
// Register unknown type builder.
panelBuilder.build();
}
/**
* This method updates filter of elements based on tabs.
*/
private void updateFilters()
{
Comparator<MaterialRecord> sorter;
switch (this.activeFilter)
{
case VALUE_ASC ->
sorter = (o1, o2) ->
{
if (o1.value().equals(o2.value()))
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
}
else
{
return Integer.compare(o1.value(), o2.value());
}
};
case VALUE_DESC ->
sorter = (o1, o2) ->
{
if (o1.value().equals(o2.value()))
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
}
else
{
return Integer.compare(o2.value(), o1.value());
}
};
case NAME_DESC ->
sorter = (o1, o2) ->
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o2Name, o1Name);
};
default ->
sorter = (o1, o2) ->
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
};
}
this.materialRecordList.sort(sorter);
if (!this.searchText.isBlank())
{
this.elementList = new ArrayList<>(this.materialRecordList.size());
final String text = this.searchText.toLowerCase();
this.materialRecordList.forEach(rec ->
{
if (rec.material.name().toLowerCase().contains(text) ||
Utils.prettifyObject(rec.material(), this.user).toLowerCase().contains(text))
{
this.elementList.add(rec);
}
});
}
else
{
this.elementList = this.materialRecordList;
}
this.pageIndex = 0;
}
// ---------------------------------------------------------------------
// Section: Tab Button Type
// ---------------------------------------------------------------------
/**
* Create tab button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
{
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
// Set icon
builder.icon(template.icon().clone());
}
if (template.title() != null)
{
// Set title
builder.name(this.user.getTranslation(this.world, template.title(), "[text]", this.searchText));
}
if (template.description() != null)
{
// Set description
builder.description(this.user.getTranslation(this.world, template.description(), "[text]", this.searchText));
}
// Get only possible actions, by removing all inactive ones.
List<ItemTemplateRecord.ActionRecords> activeActions = new ArrayList<>(template.actions());
activeActions.removeIf(action ->
"CLEAR".equalsIgnoreCase(action.actionType()) && this.searchText.isBlank());
// Add Click handler
builder.clickHandler((panel, user, clickType, i) ->
{
for (ItemTemplateRecord.ActionRecords action : activeActions)
{
if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
{
if ("CLEAR".equalsIgnoreCase(action.actionType()))
{
this.searchText = "";
// Update filters.
this.updateFilters();
this.build();
}
else if ("INPUT".equalsIgnoreCase(action.actionType()))
{
// Create consumer that process description change
Consumer<String> consumer = value ->
{
if (value != null)
{
this.searchText = value;
this.updateFilters();
}
this.build();
};
// start conversation
ConversationUtils.createStringInput(consumer,
user,
user.getTranslation("level.conversations.write-search"),
user.getTranslation("level.conversations.search-updated"));
}
}
}
return true;
});
// Collect tooltips.
List<String> tooltips = activeActions.stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.world, action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty())
{
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
builder.glow(!this.searchText.isBlank());
return builder.build();
}
/**
* Create next button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
{
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
// Set icon
builder.icon(template.icon().clone());
}
String filterName = String.valueOf(template.dataMap().get("filter"));
final String reference = "level.gui.buttons.filters.";
if (template.title() != null)
{
// Set title
builder.name(this.user.getTranslation(this.world, template.title()));
}
else
{
builder.name(this.user.getTranslation(this.world, reference + filterName.toLowerCase() + ".name"));
}
if (template.description() != null)
{
// Set description
builder.description(this.user.getTranslation(this.world, template.description()));
}
else
{
builder.name(this.user.getTranslation(this.world, reference + filterName.toLowerCase() + ".description"));
}
// Get only possible actions, by removing all inactive ones.
List<ItemTemplateRecord.ActionRecords> activeActions = new ArrayList<>(template.actions());
activeActions.removeIf(action -> {
if (this.activeFilter.name().startsWith(filterName))
{
return this.activeFilter.name().endsWith("ASC") && "ASC".equalsIgnoreCase(action.actionType()) ||
this.activeFilter.name().endsWith("DESC") && "DESC".equalsIgnoreCase(action.actionType());
}
else
{
return "DESC".equalsIgnoreCase(action.actionType());
}
});
// Add Click handler
builder.clickHandler((panel, user, clickType, i) ->
{
for (ItemTemplateRecord.ActionRecords action : activeActions)
{
if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
{
if ("ASC".equalsIgnoreCase(action.actionType()))
{
this.activeFilter = Enums.getIfPresent(Filter.class, filterName + "_ASC").or(Filter.NAME_ASC);
// Update filters.
this.updateFilters();
this.build();
}
else if ("DESC".equalsIgnoreCase(action.actionType()))
{
this.activeFilter = Enums.getIfPresent(Filter.class, filterName + "_DESC").or(Filter.NAME_DESC);
// Update filters.
this.updateFilters();
this.build();
}
}
}
return true;
});
// Collect tooltips.
List<String> tooltips = activeActions.stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.world, action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty())
{
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
builder.glow(this.activeFilter.name().startsWith(filterName.toUpperCase()));
return builder.build();
}
// ---------------------------------------------------------------------
// Section: Create common buttons
// ---------------------------------------------------------------------
/**
* Create next button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
{
long size = this.elementList.size();
if (size <= slot.amountMap().getOrDefault(BLOCK, 1) ||
1.0 * size / slot.amountMap().getOrDefault(BLOCK, 1) <= this.pageIndex + 1)
{
// There are no next elements
return null;
}
int nextPageIndex = this.pageIndex + 2;
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
ItemStack clone = template.icon().clone();
if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false)))
{
clone.setAmount(nextPageIndex);
}
builder.icon(clone);
}
if (template.title() != null)
{
builder.name(this.user.getTranslation(this.world, template.title()));
}
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.world, template.description(),
TextVariables.NUMBER, String.valueOf(nextPageIndex)));
}
// Add ClickHandler
builder.clickHandler((panel, user, clickType, i) ->
{
for (ItemTemplateRecord.ActionRecords action : template.actions())
{
if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
&& "NEXT".equalsIgnoreCase(action.actionType()))
{
this.pageIndex++;
this.build();
}
}
// Always return true.
return true;
});
// Collect tooltips.
List<String> tooltips = template.actions().stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.world, action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty())
{
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
return builder.build();
}
/**
* Create previous button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
{
if (this.pageIndex == 0)
{
// There are no next elements
return null;
}
int previousPageIndex = this.pageIndex;
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
ItemStack clone = template.icon().clone();
if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false)))
{
clone.setAmount(previousPageIndex);
}
builder.icon(clone);
}
if (template.title() != null)
{
builder.name(this.user.getTranslation(this.world, template.title()));
}
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.world, template.description(),
TextVariables.NUMBER, String.valueOf(previousPageIndex)));
}
// Add ClickHandler
builder.clickHandler((panel, user, clickType, i) ->
{
for (ItemTemplateRecord.ActionRecords action : template.actions())
{
if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
&& "PREVIOUS".equalsIgnoreCase(action.actionType()))
{
this.pageIndex--;
this.build();
}
}
// Always return true.
return true;
});
// Collect tooltips.
List<String> tooltips = template.actions().stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.world, action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty())
{
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
return builder.build();
}
// ---------------------------------------------------------------------
// Section: Create Material Button
// ---------------------------------------------------------------------
/**
* Create material button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
{
if (this.elementList.isEmpty())
{
// Does not contain any generators.
return null;
}
int index = this.pageIndex * slot.amountMap().getOrDefault(BLOCK, 1) + slot.slot();
if (index >= this.elementList.size())
{
// Out of index.
return null;
}
return this.createMaterialButton(template, this.elementList.get(index));
}
/**
* This method creates button for material.
*
* @param template the template of the button
* @param materialRecord materialRecord which button must be created.
* @return PanelItem for generator tier.
*/
private PanelItem createMaterialButton(ItemTemplateRecord template,
MaterialRecord materialRecord)
{
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
builder.icon(template.icon().clone());
}
else
{
builder.icon(PanelUtils.getMaterialItem(materialRecord.material()));
}
if (materialRecord.value() <= 64 && materialRecord.value() > 0)
{
builder.amount(materialRecord.value());
}
if (template.title() != null)
{
builder.name(this.user.getTranslation(this.world, template.title(),
"[material]", Utils.prettifyObject(materialRecord.material(), this.user)));
}
String description = Utils.prettifyDescription(materialRecord.material(), this.user);
final String reference = "level.gui.buttons.material.";
String blockId = this.user.getTranslationOrNothing(reference + "id",
"[id]", materialRecord.material().name());
String value = this.user.getTranslationOrNothing(reference + "value",
TextVariables.NUMBER, String.valueOf(materialRecord.value()));
String underWater;
if (this.addon.getSettings().getUnderWaterMultiplier() > 1.0)
{
underWater = this.user.getTranslationOrNothing(reference + "underwater",
TextVariables.NUMBER, String.valueOf(materialRecord.value() * this.addon.getSettings().getUnderWaterMultiplier()));
}
else
{
underWater = "";
}
String limit = materialRecord.limit() > 0 ? this.user.getTranslationOrNothing(reference + "limit",
TextVariables.NUMBER, String.valueOf(materialRecord.limit())) : "";
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.world, template.description(),
"[description]", description,
"[id]", blockId,
"[value]", value,
"[underwater]", underWater,
"[limit]", limit).
replaceAll("(?m)^[ \\t]*\\r?\\n", "").
replaceAll("(?<!\\\\)\\|", "\n").
replace("\\\\\\|", "|")); // Non regex
}
builder.clickHandler((panel, user1, clickType, i) -> {
addon.log("Material: " + materialRecord.material());
return true;
});
return builder.build();
}
// ---------------------------------------------------------------------
// Section: Other Methods
// ---------------------------------------------------------------------
/**
* This method is used to open UserPanel outside this class. It will be much easier to open panel with single method
* call then initializing new object.
*
* @param addon Level object
* @param world World where user is operating
* @param user User who opens panel
*/
public static void openPanel(Level addon,
World world,
User user)
{
new ValuePanel(addon, world, user).build();
}
// ---------------------------------------------------------------------
// Section: Enums
// ---------------------------------------------------------------------
/**
* Sorting order of blocks.
*/
private enum Filter
{
/**
* By name asc
*/
NAME_ASC,
/**
* By name desc
*/
NAME_DESC,
/**
* By value asc
*/
VALUE_ASC,
/**
* By value desc
*/
VALUE_DESC,
}
private record MaterialRecord(Material material, Integer value, Integer limit)
{
}
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
private static final String BLOCK = "BLOCK";
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable allows to access addon object.
*/
private final Level addon;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds a world to which gui referee.
*/
private final World world;
/**
* This variable stores the list of elements to display.
*/
private final List<MaterialRecord> materialRecordList;
/**
* This variable stores the list of elements to display.
*/
private List<MaterialRecord> elementList;
/**
* This variable holds current pageIndex for multi-page generator choosing.
*/
private int pageIndex;
/**
* This variable stores which tab currently is active.
*/
private String searchText;
/**
* This variable stores active filter for items.
*/
private Filter activeFilter;
}

View File

@ -54,6 +54,6 @@ public class TopTenRequestHandler extends AddonRequestHandler {
} }
// No null check required // No null check required
return addon.getManager().getTopTen(Bukkit.getWorld((String) map.get(WORLD_NAME)), Level.TEN); return addon.getManager().getTopTen(Bukkit.getWorld((String) map.get(WORLD_NAME)), 10);
} }
} }

View File

@ -1,30 +0,0 @@
package world.bentobox.level.util;
import java.time.Instant;
import java.util.Map;
/**
* Cache for top tens
*/
public class CachedData {
private Map<String, Long> cachedMap;
private Instant lastUpdated;
public CachedData(Map<String, Long> cachedMap, Instant lastUpdated) {
this.cachedMap = cachedMap;
this.lastUpdated = lastUpdated;
}
public Map<String, Long> getCachedMap() {
return cachedMap;
}
public Instant getLastUpdated() {
return lastUpdated;
}
public void updateCache(Map<String, Long> newMap, Instant newUpdateTime) {
this.cachedMap = newMap;
this.lastUpdated = newUpdateTime;
}
}

View File

@ -1,126 +0,0 @@
//
// Created by BONNe
// Copyright - 2021
//
package world.bentobox.level.util;
import java.util.function.Consumer;
import org.bukkit.conversations.ConversationAbandonedListener;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ConversationFactory;
import org.bukkit.conversations.MessagePrompt;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
public class ConversationUtils
{
// ---------------------------------------------------------------------
// Section: Conversation API implementation
// ---------------------------------------------------------------------
private ConversationUtils() {} // Private constructor as this is a utility class only with static methods
/**
* This method will close opened gui and writes question in chat. After players answers on question in chat, message
* will trigger consumer and gui will reopen.
*
* @param consumer Consumer that accepts player output text.
* @param question Message that will be displayed in chat when player triggers conversion.
* @param user User who is targeted with current confirmation.
*/
public static void createStringInput(Consumer<String> consumer,
User user,
@NonNull String question,
@Nullable String successMessage)
{
// Text input message.
StringPrompt stringPrompt = new StringPrompt()
{
@Override
public @NonNull String getPromptText(@NonNull ConversationContext context)
{
user.closeInventory();
return question;
}
@Override
public @NonNull Prompt acceptInput(@NonNull ConversationContext context, @Nullable String input)
{
consumer.accept(input);
return ConversationUtils.endMessagePrompt(successMessage);
}
};
new ConversationFactory(BentoBox.getInstance()).
withPrefix(context -> user.getTranslation("level.conversations.prefix")).
withFirstPrompt(stringPrompt).
// On cancel conversation will be closed.
withLocalEcho(false).
withTimeout(90).
withEscapeSequence(user.getTranslation("level.conversations.cancel-string")).
// Use null value in consumer to detect if user has abandoned conversation.
addConversationAbandonedListener(ConversationUtils.getAbandonListener(consumer, user)).
buildConversation(user.getPlayer()).
begin();
}
/**
* This is just a simple end message prompt that displays requested message.
*
* @param message Message that will be displayed.
* @return MessagePrompt that displays given message and exists from conversation.
*/
private static MessagePrompt endMessagePrompt(@Nullable String message)
{
return new MessagePrompt()
{
@Override
public @NonNull String getPromptText(@NonNull ConversationContext context)
{
return message == null ? "" : message;
}
@Override
protected @Nullable Prompt getNextPrompt(@NonNull ConversationContext context)
{
return Prompt.END_OF_CONVERSATION;
}
};
}
/**
* This method creates and returns abandon listener for every conversation.
*
* @param consumer Consumer which must return null value.
* @param user User who was using conversation.
* @return ConversationAbandonedListener instance.
*/
private static ConversationAbandonedListener getAbandonListener(Consumer<?> consumer, User user)
{
return abandonedEvent ->
{
if (!abandonedEvent.gracefulExit())
{
consumer.accept(null);
// send cancell message
abandonedEvent.getContext().getForWhom().sendRawMessage(
user.getTranslation("level.conversations.prefix") +
user.getTranslation("level.conversations.cancelled"));
}
};
}
}

View File

@ -1,229 +0,0 @@
//
// Created by BONNe
// Copyright - 2021
//
package world.bentobox.level.util;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.permissions.PermissionAttachmentInfo;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.hooks.LangUtilsHook;
public class Utils
{
private static final String LEVEL_MATERIALS = "level.materials.";
private Utils() {} // Private constructor as this is a utility class only with static methods
/**
* This method sends a message to the user with appended "prefix" text before message.
* @param user User who receives message.
* @param translationText Translation text of the message.
* @param parameters Parameters for the translation text.
*/
public static void sendMessage(User user, String translationText, String... parameters)
{
user.sendMessage(user.getTranslation( "level.conversations.prefix") +
user.getTranslation( translationText, parameters));
}
/**
* This method gets string value of given permission prefix. If user does not have given permission or it have all
* (*), then return default value.
*
* @param user User who's permission should be checked.
* @param permissionPrefix Prefix that need to be found.
* @param defaultValue Default value that will be returned if permission not found.
* @return String value that follows permissionPrefix.
*/
public static String getPermissionValue(User user, String permissionPrefix, String defaultValue)
{
if (user.isPlayer())
{
if (permissionPrefix.endsWith("."))
{
permissionPrefix = permissionPrefix.substring(0, permissionPrefix.length() - 1);
}
String permPrefix = permissionPrefix + ".";
List<String> permissions = user.getEffectivePermissions().stream().
map(PermissionAttachmentInfo::getPermission).
filter(permission -> permission.startsWith(permPrefix)).
toList();
for (String permission : permissions)
{
if (permission.contains(permPrefix + "*"))
{
// * means all. So continue to search more specific.
continue;
}
String[] parts = permission.split(permPrefix);
if (parts.length > 1)
{
return parts[1];
}
}
}
return defaultValue;
}
/**
* This method allows to get next value from array list after given value.
*
* @param values Array that should be searched for given value.
* @param currentValue Value which next element should be found.
* @param <T> Instance of given object.
* @return Next value after currentValue in values array.
*/
public static <T> T getNextValue(T[] values, T currentValue)
{
for (int i = 0; i < values.length; i++)
{
if (values[i].equals(currentValue))
{
if (i + 1 == values.length)
{
return values[0];
}
else
{
return values[i + 1];
}
}
}
return currentValue;
}
/**
* This method allows to get previous value from array list after given value.
*
* @param values Array that should be searched for given value.
* @param currentValue Value which previous element should be found.
* @param <T> Instance of given object.
* @return Previous value before currentValue in values array.
*/
public static <T> T getPreviousValue(T[] values, T currentValue)
{
for (int i = 0; i < values.length; i++)
{
if (values[i].equals(currentValue))
{
if (i > 0)
{
return values[i - 1];
}
else
{
return values[values.length - 1];
}
}
}
return currentValue;
}
/**
* Prettify Material object for user.
* @param object Object that must be pretty.
* @param user User who will see the object.
* @return Prettified string for Material.
*/
public static String prettifyObject(Material object, User user)
{
// Nothing to translate
if (object == null)
{
return "";
}
// Find addon structure with:
// [addon]:
// materials:
// [material]:
// name: [name]
String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".name");
if (!translation.isEmpty())
{
// We found our translation.
return translation;
}
// Find addon structure with:
// [addon]:
// materials:
// [material]: [name]
translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase());
if (!translation.isEmpty())
{
// We found our translation.
return translation;
}
// Find general structure with:
// materials:
// [material]: [name]
translation = user.getTranslationOrNothing("materials." + object.name().toLowerCase());
if (!translation.isEmpty())
{
// We found our translation.
return translation;
}
// Use Lang Utils Hook to translate material
return LangUtilsHook.getMaterialName(object, user);
}
/**
* Prettify Material object description for user.
* @param object Object that must be pretty.
* @param user User who will see the object.
* @return Prettified description string for Material.
*/
public static String prettifyDescription(Material object, User user)
{
// Nothing to translate
if (object == null)
{
return "";
}
// Find addon structure with:
// [addon]:
// materials:
// [material]:
// description: [text]
String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".description");
if (!translation.isEmpty())
{
// We found our translation.
return translation;
}
// No text to return.
return "";
}
}

View File

@ -2,7 +2,7 @@ name: Level
main: world.bentobox.level.Level main: world.bentobox.level.Level
version: ${version}${build.number} version: ${version}${build.number}
icon: DIAMOND icon: DIAMOND
api-version: 2.4.0 api-version: 1.14
authors: tastybento authors: tastybento

View File

@ -43,6 +43,10 @@ blocks:
BARREL: 2 BARREL: 2
BARRIER: 0 BARRIER: 0
BASALT: 1 BASALT: 1
BLACKSTONE: 500
BLACKSTONE_SLAB: 0
BLACKSTONE_STAIRS: 1
BLACKSTONE_WALL: 1
BEDROCK: 0 BEDROCK: 0
BEETROOTS: 1 BEETROOTS: 1
BELL: 100 BELL: 100
@ -246,7 +250,6 @@ blocks:
DIORITE_STAIRS: 1 DIORITE_STAIRS: 1
DIORITE_WALL: 1 DIORITE_WALL: 1
DIRT: 3 DIRT: 3
DIRT_PATH: 4
DISPENSER: 5 DISPENSER: 5
DRAGON_EGG: 150 DRAGON_EGG: 150
DRAGON_HEAD: 1 DRAGON_HEAD: 1
@ -287,8 +290,9 @@ blocks:
GRANITE_SLAB: 1 GRANITE_SLAB: 1
GRANITE_STAIRS: 1 GRANITE_STAIRS: 1
GRANITE_WALL: 1 GRANITE_WALL: 1
SHORT_GRASS: 4 GRASS: 4
GRASS_BLOCK: 4 GRASS_BLOCK: 4
GRASS_PATH: 4
GRAVEL: 1 GRAVEL: 1
GRAY_BANNER: 2 GRAY_BANNER: 2
GRAY_BED: 6 GRAY_BED: 6
@ -778,58 +782,6 @@ worlds:
ANDESITE: 0 ANDESITE: 0
DIORITE: 0 DIORITE: 0
acidisland_world: acidisland_world:
BRAIN_CORAL: 0
BRAIN_CORAL_BLOCK: 0
BRAIN_CORAL_FAN: 0
BRAIN_CORAL_WALL_FAN: 0
BUBBLE_CORAL: 0
BUBBLE_CORAL_BLOCK: 0
BUBBLE_CORAL_FAN: 0
BUBBLE_CORAL_WALL_FAN: 0
DEAD_BRAIN_CORAL: 0
DEAD_BRAIN_CORAL_BLOCK: 0
DEAD_BRAIN_CORAL_FAN: 0
DEAD_BRAIN_CORAL_WALL_FAN: 0
DEAD_BUBBLE_CORAL: 0
DEAD_BUBBLE_CORAL_BLOCK: 0
DEAD_BUBBLE_CORAL_FAN: 0
DEAD_BUBBLE_CORAL_WALL_FAN: 0
FIRE_CORAL: 0
FIRE_CORAL_BLOCK: 0
FIRE_CORAL_FAN: 0
FIRE_CORAL_WALL_FAN: 0
DEAD_FIRE_CORAL: 0
DEAD_FIRE_CORAL_BLOCK: 0
DEAD_FIRE_CORAL_FAN: 0
DEAD_FIRE_CORAL_WALL_FAN: 0
HORN_CORAL: 0
HORN_CORAL_BLOCK: 0
HORN_CORAL_FAN: 0
HORN_CORAL_WALL_FAN: 0
DEAD_HORN_CORAL: 0
DEAD_HORN_CORAL_BLOCK: 0
DEAD_HORN_CORAL_FAN: 0
DEAD_HORN_CORAL_WALL_FAN: 0
TUBE_CORAL: 0
TUBE_CORAL_BLOCK: 0
TUBE_CORAL_FAN: 0
TUBE_CORAL_WALL_FAN: 0
DEAD_TUBE_CORAL: 0
DEAD_TUBE_CORAL_BLOCK: 0
DEAD_TUBE_CORAL_FAN: 0
DEAD_TUBE_CORAL_WALL_FAN: 0
SAND: 0 SAND: 0
SANDSTONE: 0 SANDSTONE: 0
RED_SAND: 0
ICE: 0 ICE: 0
AMETHYST_CLUSTER: 0
AMETHYST_BLOCK: 0
LARGE_AMETHYST_BUD: 0
MEDIUM_AMETHYST_BUD: 0
SMALL_AMETHYST_BUD: 0
BUDDING_AMETHYST: 0
SEA_PICKLE: 0
CALCITE: 0
TALL_SEAGRASS: 0
SEAGRASS: 0
SMOOTH_BASALT: 0

View File

@ -3,10 +3,8 @@
# #
# Disabled Game Mode Addons # Disabled Game Mode Addons
# Level will NOT hook into these game mode addons. # Level will NOT hook into these game mode addons.
disabled-game-modes: [] disabled-game-modes:
# - AOneBlock
# When executing level command from console, should a report be shown?
log-report-to-console: true
# #
# Number of concurrent island calculations # Number of concurrent island calculations
# If your CPU can handle it, you can run parallel island calcs if there are more than one in the queue # If your CPU can handle it, you can run parallel island calcs if there are more than one in the queue
@ -53,7 +51,7 @@ levelcost: 100
# Island level calculation formula # Island level calculation formula
# blocks - the sum total of all block values, less any death penalty # blocks - the sum total of all block values, less any death penalty
# level_cost - in a linear equation, the value of one level # level_cost - in a linear equation, the value of one level
# This formula can include +,=,*,/,sqrt,^,sin,cos,tan,log (natural log). Result will always be rounded to a long integer # This formula can include +,=,*,/,sqrt,^,sin,cos,tan. Result will always be rounded to a long integer
# for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost) # for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost)
level-calc: blocks / level_cost level-calc: blocks / level_cost
# #

View File

@ -3,42 +3,32 @@ admin:
level: level:
parameters: "<Spieler>" parameters: "<Spieler>"
description: Berechne das Insel Level für den Spieler description: Berechne das Insel Level für den Spieler
levelstatus:
islands-in-queue: "& a Inseln in der Warteschlange: [number]"
top: top:
description: Zeige die Top-10 Liste
unknown-world: "&cUnbekannte Welt!"
display: "&f[rank]. &a[name] &7- &b[level]"
remove: remove:
description: entferne Spieler von Top-10 description: entferne Spieler von Top-10
parameters: "<Spieler>" parameters: "<Spieler>"
description: Zeige die Top-10 Liste
unknown-world: "&cUnbekannte Welt!"
display: "&f[rank]. &a[name] &7- &b[level]"
island: island:
level: level:
parameters: "[Spieler]" parameters: "[Spieler]"
description: Berechne dein Insel Level oder zeige das Level von [Spieler] description: Berechne dein Insel Level oder zeige das Level von [Spieler]
calculating: "&aBerechne Level..."
estimated-wait: "& a Geschätzte Wartezeit: [number] Sekunden"
in-queue: "& a Sie sind Nummer [number] in der Warteschlange"
island-level-is: "&aInsel Level: &b[level]"
required-points-to-next-level: "&a[points] Punkte werden für das nächste Level required-points-to-next-level: "&a[points] Punkte werden für das nächste Level
benötigt" benötigt"
calculating: "&aBerechne Level..."
island-level-is: "&aInsel Level: &b[level]"
deaths: "&c([number] Tode)" deaths: "&c([number] Tode)"
cooldown: "&cDu musst &b[time] &csekunden warten bevor du das erneut machen kannst." cooldown: "&cDu musst &b[time] &csekunden warten bevor du das erneut machen kannst."
value:
description: Zeige den Wert jedes Blockes
success-underwater: "&7Wert des Blockes Unterwasser: &e[value]"
success: "&7Wert: &e[value]"
empty-hand: "&cDu hast keinen Block in der Hand"
no-value: "&cDas Item hat kein wert!"
top: top:
description: Zeige die Top-10 description: Zeige die Top-10
gui-title: "&aTop Zehn" gui-title: "&aTop Zehn"
gui-heading: "&6[name]: &B[rank]" gui-heading: "&6[name]: &B[rank]"
island-level: "&BLevel [level]" island-level: "&BLevel [level]"
warp-to: "&ATeleportiere zu [name]'s Insel" warp-to: "&ATeleportiere zu [name]'s Insel"
level-details:
above-sea-level-blocks: Blöcke über dem Meeresspiegel
spawners: Spawner
underwater-blocks: Unterwasserblöcke
all-blocks: Alle Blöcke
no-island: "&c Keine Insel!"
value:
description: Zeige den Wert jedes Blockes
success: "&7Wert: &e[value]"
success-underwater: "&7Wert des Blockes Unterwasser: &e[value]"
empty-hand: "&cDu hast keinen Block in der Hand"
no-value: "&cDas Item hat kein wert!"

View File

@ -22,18 +22,8 @@ admin:
remove: remove:
description: "remove player from Top Ten" description: "remove player from Top Ten"
parameters: "<player>" parameters: "<player>"
stats:
description: "show stats on islands on this server"
title: "Server Island Stats"
world: "&a [name]"
no-data: "&c No data to process."
average-level: "Average Island Level: [number]"
median-level: "Median Island Level: [number]"
mode-level: "Mode Island Level: [number]"
highest-level: "Highest Island Level: [number]"
lowest-level: "Lowest Island Level: [number]"
distribution: "Island Level Distribution:"
islands: "islands"
island: island:
level: level:
parameters: "[player]" parameters: "[player]"
@ -42,7 +32,7 @@ island:
estimated-wait: "&a Estimated wait: [number] seconds" estimated-wait: "&a Estimated wait: [number] seconds"
in-queue: "&a You are number [number] in the queue" in-queue: "&a You are number [number] in the queue"
island-level-is: "&a Island level is &b[level]" island-level-is: "&a Island level is &b[level]"
required-points-to-next-level: "&a Level progress: &6 [progress]&b /&e [levelcost] &a points" required-points-to-next-level: "&a [points] points required until the next level"
deaths: "&c([number] deaths)" deaths: "&c([number] deaths)"
cooldown: "&c You must wait &b[time] &c seconds until you can do that again" cooldown: "&c You must wait &b[time] &c seconds until you can do that again"
in-progress: "&6 Island level calculation is in progress..." in-progress: "&6 Island level calculation is in progress..."
@ -65,149 +55,9 @@ island:
syntax: "[name] x [number]" syntax: "[name] x [number]"
hint: "&c Run level to see the block report" hint: "&c Run level to see the block report"
level:
commands:
value: value:
parameters: "[hand|<material>]" description: "shows the value of any block"
description: "shows the value of blocks. Add 'hand' at the end to display value for item in hand." success: "&7 The value of this block is: &e[value]"
gui: success-underwater: "&7 The value of this block below sea-level: &e[value]"
titles:
top: "&0&l Top Islands"
detail-panel: "&0&l [name]'s island"
value-panel: "&0&l Block Values"
buttons:
island:
empty: '&f&l [name]. place'
name: '&f&l [name]'
description: |-
[owner]
[members]
[place]
[level]
# Text that is replacing [name] if island do not have a name
owners-island: "[player]'s Island"
# Text for [owner] in description.
owner: "&7&l Owner: &r&b [player]"
# Title before listing members for [members] in description
members-title: "&7&l Members:"
# List each member under the title for [members] in description
member: "&b - [player]"
# Name of unknown player.
unknown: "unknown"
# Section for parsing [place]
place: "&7&o [number]. &r&7 place"
# Section for parsing [level]
level: "&7 Level: &o [number]"
material:
name: "&f&l [number] x [material]"
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: "&7 Block id: &e [id]"
value: "&7 Block value: &e [number]"
limit: "&7 Block limit: &e [number]"
count: "&7 Number of blocks: &e [number]"
calculated: "&7 Calculated value: &e [number]"
all_blocks:
name: "&f&l All Blocks"
description: |-
&7 Display all blocks
&7 on island.
above_sea_level:
name: "&f&l Blocks Above Sea Level"
description: |-
&7 Display only blocks
&7 that are above sea
&7 level.
underwater:
name: "&f&l Blocks Under Sea level"
description: |-
&7 Display only blocks
&7 that are bellow sea
&7 level.
spawner:
name: "&f&l Spawners"
description: |-
&7 Display only spawners.
filters:
name:
name: "&f&l Sort by Name"
description: |-
&7 Sort all blocks by name.
value:
name: "&f&l Sort by Value"
description: |-
&7 Sort all blocks by their value.
count:
name: "&f&l Sort by Count"
description: |-
&7 Sort all blocks by their amount.
value:
name: "&f&l [material]"
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: "&7 Block id: &e [id]"
value: "&7 Block value: &e [number]"
underwater: "&7 Bellow sea level: &e [number]"
limit: "&7 Block limit: &e [number]"
# Button that is used in multi-page GUIs which allows to return to previous page.
previous:
name: "&f&l Previous Page"
description: |-
&7 Switch to [number] page
# Button that is used in multi-page GUIs which allows to go to next page.
next:
name: "&f&l Next Page"
description: |-
&7 Switch to [number] page
search:
name: "&f&l Search"
description: |-
&7 Search for a specific
&7 value.
search: "&b Value: [value]"
tips:
click-to-view: "&e Click &7 to view."
click-to-previous: "&e Click &7 to view previous page."
click-to-next: "&e Click &7 to view next page."
click-to-select: "&e Click &7 to select."
left-click-to-cycle-up: "&e Left Click &7 to cycle up."
right-click-to-cycle-down: "&e Right Click &7 to cycle down."
left-click-to-change: "&e Left Click &7 to edit."
right-click-to-clear: "&e Right Click &7 to clear."
click-to-asc: "&e Click &7 to sort in increasing order."
click-to-desc: "&e Click &7 to sort in decreasing order."
click-to-warp: "&e Click &7 to warp."
click-to-visit: "&e Click &7 to visit."
right-click-to-visit: "&e Right Click &7 to visit."
conversations:
# Prefix for messages that are send from server.
prefix: "&l&6 [BentoBox]: &r"
no-data: "&c Run level to see the block report."
# String that allows to cancel conversation. (can be only one)
cancel-string: "cancel"
# List of strings that allows to exit conversation. (separated with ,)
exit-string: "cancel, exit, quit"
# Message that asks for search value input.
write-search: "&e Please enter a search value. (Write 'cancel' to exit)"
# Message that appears after updating search value.
search-updated: "&a Search value updated."
# Message that is sent to user when conversation is cancelled.
cancelled: "&c Conversation cancelled!"
# Message that is sent to user when given material does not have any value.
no-value: "&c That item has no value."
# Message that is sent to user when requested material does not exist.
unknown-item: "&c The '[material]' does not exist in game."
# Messages that is sent to user when requesting value for a specific material.
value: "&7 The value of '[material]' is: &e[value]"
value-underwater: "&7 The value of '[material]' below sea-level: &e[value]"
# Message that is sent to user when he does not hold any items in hand.
empty-hand: "&c There are no blocks in your hand" empty-hand: "&c There are no blocks in your hand"
no-value: "&c That item has no value."

View File

@ -1,172 +1,37 @@
--- ###########################################################################################################
# Este es un archivo YML. Tenga cuidado al editar. Revisa tus ediciones en un verificador de YAML como #
# el de http://yaml-online-parser.appspot.com #
###########################################################################################################
admin: admin:
level: level:
parameters: "<player>" parameters: "<player>"
description: Calcula el nivel de la isla del jugador description: "calcula el nivel de la isla para el jugador"
sethandicap:
parameters: "<player> <handicap>"
description: Define la desventaja de la isla, usualmente el nivel inicial para
nuevas islas
changed: "&aDesventaja inicial de la isla cambiado de [number] a [new_number]."
invalid-level: "&cNúmero no válido. Usa un número entero."
levelstatus:
description: Muestra cuantas islas hay en la cola para escanear
islands-in-queue: "&aIslas en cola: [number]"
top: top:
description: Muestra la lista de las diez primeras islas description: "mostrar la lista de los diez primeros"
unknown-world: "&c¡Mundo desconocido!" unknown-world: "&cMundo Desconocido!"
display: "&f[rank]. &a[name] &7- &b[level]" display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: Elimina a un jugador de los diez primeros
parameters: "<jugador>"
island: island:
level: level:
parameters: "[player]" parameters: "[player]"
description: Calcula tu nivel de isla o muestra el nivel de [player] description: "calcula tu nivel de isla o muestra el nivel de [player]"
calculating: "&aCalculando nivel..." calculating: "&aCalculando nivel..."
estimated-wait: "&aEspera estimada: [number] segundos"
in-queue: "&aEstás en el puesto [number] de la cola"
island-level-is: "&aNivel de isla es de &b[level]" island-level-is: "&aNivel de isla es de &b[level]"
required-points-to-next-level: "&a[points] Puntos requeridos hasta el siguiente required-points-to-next-level: "&a[points] Puntos requeridos hasta el siguiente nivel."
nivel."
deaths: "&c([number] Muertes)" deaths: "&c([number] Muertes)"
cooldown: "&cDebes esperar &b[time] &csegundos para poder volver a hacer esto." cooldown: "&cDebes esperar &b[time] &csegundos para poder volver a hacer eso"
in-progress: "&6El Calculo del nivel de la islas está en progreso..."
time-out: "&cEl calculo del nivel de la isla está tardando. Intente más tarde."
top: top:
description: Muestra el top de islas description: "mostrar los diez primeros"
gui-title: "&aTop diez" gui-title: "&aDiez primeros"
gui-heading: "&6[name]: &b[rank]" gui-heading: "&6[name]: &B[rank]"
island-level: "&bNivel [level]" island-level: "&BNivel [level]"
warp-to: "&aLlevándote a la isla de [name]" warp-to: "&ATeletransportandote a la isla de [name]"
level-details:
above-sea-level-blocks: Bloques sobre el nivel del mar
spawners: Spawners
underwater-blocks: Bloques debajo del nivel del mar
all-blocks: Todos los bloques
no-island: "&c¡Sin isla!"
names-island: Isla de [name]
syntax: "[name] x [number]"
hint: "&cEscriba /level para ver el recuento de bloques"
level:
commands:
value: value:
parameters: "[hand|<material>]" description: "muestra el valor de cualquier bloque"
description: muestra el valor de los bloques. Añade 'hand' al final para mostrar success: "&7El valor de este bloque es: &e[value]"
el valor del bloque de la mano. success-underwater: "&7El valor de este bloque bajo el nivel del mar: &e[value]"
gui:
titles:
top: "&0&lTop de islas"
detail-panel: "&0&lIsla de [name]"
value-panel: "&0&l Valores de los Bloques"
buttons:
island:
empty: "&f&l[name]. lugar"
name: "&f&l[name]"
description: |-
[owner]
[members]
[place]
[level]
owners-island: Isla de [player]
owner: "&7&l Dueño: &r&b[player]"
members-title: "&7&l Miembros:"
member: "&b - [player]"
unknown: " desconocido"
place: "&7&o [number]. &r&7lugar"
level: "&7 Nivel: &o[number]"
material:
name: "&f&l[number] x [material]"
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: "&7 ID del bloque: &e[id]"
value: "&7 Valor del bloque: &e[number]"
limit: "&7 Limite de bloques: &e[number]"
count: "&7 Número de bloques: &e[number]"
calculated: "&7 Valor calculado: &e[number]"
all_blocks:
name: "&f&lTodos los bloques"
description: |-
&7 Muestra todos los
&7 bloques en la isla.
above_sea_level:
name: "&f&lBloques sobre el nivel del mar"
description: |-
&7 Muestra solo bloques
&7 que estén sobre el
&7 nivel del mar.
underwater:
name: "&f&lBloques debajo del nivel del mar"
description: |-
&7 Muestra solo bloques
&7 que estén debajo del
&7 nivel del mar.
spawner:
name: "&f&lSpawners"
description: "&7Mostrar solo spawners."
filters:
name:
name: "&f&lOrdenar por nombre"
description: "&7Ordenar todos los bloques por nombre."
value:
name: "&f&lOrdenar por valor"
description: "&7Ordenar todos los bloques por valor."
count:
name: "&f&lOrdenar por cantidad"
description: "&7Ordenar todos los bloques por cantidad."
value:
name: "&f&l [material]"
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: "&7 ID de Bloque: &e [id]"
value: "&7 Valor del Bloque: &e [number]"
underwater: "&7 Por debajo del nivel del mar: &e [number]"
limit: "&7 Límite de bloque: &e [number]"
previous:
name: "&f&lPágina anterior"
description: "&7Cambiar a la página [number]"
next:
name: "&f&lSiguiente página"
description: "&7Cambiar a la página [number]"
search:
name: "&f&l Buscar"
description: |-
&7 Buscar un determinado
&7 valor.
search: "&b Valor: [value]"
tips:
click-to-view: "&eClic &7para ver."
click-to-previous: "&eClic &7 para ir a la página anterior."
click-to-next: "&eClic &7 para ir a la siguiente página."
click-to-select: "&eClic &7 para seleccionar."
left-click-to-cycle-up: "&eClic izquierdo &7para ir hacia arriba."
right-click-to-cycle-down: "&eClic derecho &7para ir hacia abajo."
left-click-to-change: "&e Clic Izquierdo &7 para editar."
right-click-to-clear: "&e Clic Derecho &7 para borrar."
click-to-asc: "&e Clic &7 para ordenar de forma creciente."
click-to-desc: "&e Clic &7 para ordenar de forma decreciente."
click-to-warp: "&e Clic &7 para teletransportarse."
click-to-visit: "&e Clic &7 para visitar."
right-click-to-visit: "&e Clic Derecho &7 para visitar."
conversations:
prefix: "&l&6[BentoBox]: &r"
no-data: "&cEscriba /level para ver el recuento de bloques."
cancel-string: cancelar
exit-string: cancelar, salir, abandonar
write-search: "&e Introduce un valor de búsqueda. (Escribe 'cancel' para salir)"
search-updated: "&a Valor de búsqueda actualizado."
cancelled: "&c ¡Conversación cancelada!"
no-value: "&c Ese ítem no tiene valor."
unknown-item: "&c El '[material]' no existe en el juego."
value: "&7 El valor de '[material]' es: &e[value]"
value-underwater: "&7 El valor de '[material]' por debajo del nivel del mar: &e[value]"
empty-hand: "&cNo hay bloques en tu mano" empty-hand: "&cNo hay bloques en tu mano"
no-value: "&cEse item no tiene valor"

View File

@ -1,175 +1,37 @@
--- ---
admin: admin:
level: level:
parameters: "<player>"
description: calcule le niveau d'île d'un joueur description: calcule le niveau d'île d'un joueur
sethandicap: parameters: "<joueur>"
parameters: "<player> <handicap>"
description: définir le handicap de l'île, généralement le niveau de l'île de
départ
changed: "&a le handicap initial de l'île est passé de [number] à [new_number]."
invalid-level: "&c Handicap non valide. Utilisez un nombre entier."
levelstatus:
description: affiche le nombre d'îles dans la file d'attente pour l'analyse
islands-in-queue: "&a Nombre d'Îles dans la file d'attente: [number]"
top: top:
description: affiche le top 10 des îles description: affiche le top 10 des îles
unknown-world: "&cMonde inconnu."
display: "&f[rank]. &a[name] &7- &b[level]" display: "&f[rank]. &a[name] &7- &b[level]"
unknown-world: "&cMonde inconnu."
remove: remove:
description: retire le joueur du top 10 description: retire le joueur du top 10
parameters: "<player>" parameters: "<joueur>"
island: island:
level: level:
parameters: "[joueur]"
description: calcule le niveau de votre île ou affiche le niveau d'un [joueur]
calculating: "&aCalcul du niveau en cours..." calculating: "&aCalcul du niveau en cours..."
estimated-wait: "&a Attente estimée: [number] seconds"
in-queue: "&a Vous êtes le numéro [number ] dans la file d'attente"
island-level-is: "&aLe niveau d'île est &b[level]"
required-points-to-next-level: "&a[points] points avant le prochain niveau"
deaths: "&c([number] morts)" deaths: "&c([number] morts)"
description: calcule le niveau de votre île ou affiche le niveau d'un [joueur]
island-level-is: "&aLe niveau d'île est &b[level]"
parameters: "[joueur]"
required-points-to-next-level: "&a[points] points avant le prochain niveau"
cooldown: "&cVous devez attendre &b[time] &csecondes avant de pouvoir refaire cooldown: "&cVous devez attendre &b[time] &csecondes avant de pouvoir refaire
cette action" cette action"
in-progress: "&6 Le calcul du niveau de l'île est en cours ..."
time-out: "&c Le calcul du niveau a pris trop de temps. Veuillez réessayer plus
tard."
top: top:
description: affiche le top 10 description: affiche le top 10
gui-title: "&aTop 10"
gui-heading: "&6[name]: &B[rank]" gui-heading: "&6[name]: &B[rank]"
gui-title: "&aTop 10"
island-level: "&BNiveau [level]" island-level: "&BNiveau [level]"
warp-to: "&ATéléportation vers l'île de [name]" warp-to: "&ATéléportation vers l'île de [name]"
level-details:
above-sea-level-blocks: Blocs au-dessus du niveau de la mer
spawners: Spawners
underwater-blocks: Blocs en-dessous du niveau de la mer
all-blocks: Total des blocs
no-island: "&c Pas d'île!"
names-island: île de [name]
syntax: "[name] x [number]"
hint: "&c Exécuter level pour voir le rapport des blocs"
level:
commands:
value: value:
parameters: "[hand|<material>]" description: affiche la valeur d'un bloc
description: affiche la valeur des blocs. Ajoutez 'hand' à la fin pour afficher success: "&7Valeur de ce bloc : &e[value]"
la valeur de l'objet en main. success-underwater: "&7Valeur de ce bloc en dessous du niveau de la mer : &e[value]"
gui: empty-hand: "&cIl n'y a aucun bloc dans votre main"
titles: no-value: "&cCet objet n'a pas de valeur."
top: "&0&l Top Islands"
detail-panel: "&0&l [name]'s island"
value-panel: "&0&l Block Values"
buttons:
island:
empty: "&f&l [name]. place"
name: "&f&l [name]"
description: |-
[owner]
[members]
[place]
[level]
owners-island: "[player]'s Island"
owner: "&7&l Propriétaire: &r&b [player]"
members-title: "&7&l Membres:"
member: "&b - [player]"
unknown: inconnue
place: "&7&o [number]. &r&7 place"
level: "&7 Level: &o [number]"
material:
name: "&f&l [number] x [material]"
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: "&7 Block id: &e [id]"
value: "&7 Block value: &e [number]"
limit: "&7 Block limit: &e [number]"
count: "&7 Nombre de blocs: &e [number]"
calculated: "&7 Valeur calculée: &e [number]"
all_blocks:
name: "&f&l Tous les blocs"
description: |-
&7 Afficher tous les blocs
&7 sur l'île.
above_sea_level:
name: "&f&l Blocs au-dessus du niveau de la mer"
description: |-
&7 Afficher uniquement les blocs
&7 qui sont au-dessus du niveau
&7 de la mer.
underwater:
name: "&f&l Blocs sous le niveau de la mer"
description: |-
&7 Afficher uniquement les blocs
&7 situés sous le niveau
&7 de la mer.
spawner:
name: "&f&l Spawners"
description: "&7 Afficher uniquement les spawners."
filters:
name:
name: "&f&l STrier par nom"
description: "&7 Trier tous les blocs par nom."
value:
name: "&f&l Trier par valeur"
description: "&7 Triez tous les blocs par leur valeur."
count:
name: "&f&l Trier par nombre"
description: "&7 Trier tous les blocs par leur montant."
value:
name: "&f&l [material]"
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: "&7 Block id: &e [id]"
value: "&7 Block value: &e [number]"
underwater: "&7 Sous le niveau de la mer : &e [number]"
limit: "&7 Block limit: &e [number]"
previous:
name: "&f&l Page précédente"
description: "&7 Passer à la page [number]"
next:
name: "&f&l Page suivante"
description: "&7 Passer à la page [number]"
search:
name: "&f&l Rechercher"
description: "&7 Recherche une valeur \n&7 spécifique."
search: "&b Valeur : [value]"
tips:
click-to-view: "&e Cliquez &7 pour afficher."
click-to-previous: "&e Cliquez &7 pour afficher la page précédente."
click-to-next: "&e Cliquez &7 pour afficher la page suivante."
click-to-select: "&e Cliquez &7 pour sélectionner."
left-click-to-cycle-up: "&e Clic gauche &7 pour monter."
right-click-to-cycle-down: "&e Clic droit &7 pour descendre."
left-click-to-change: "&e Clic gauche &7 pour éditer."
right-click-to-clear: "&e Clic droit &7 pour effacer."
click-to-asc: "&e Cliquez &7 pour trier par ordre croissant."
click-to-desc: "&e Cliquez &7 pour trier par ordre décroissant."
click-to-warp: "&e Cliquer &7 to warp."
click-to-visit: "&e Cliquer &7 pour visiter."
right-click-to-visit: "&e Clic droit&7 pour visiter."
conversations:
prefix: "&l&6 [BentoBox]: &r"
no-data: "&c Niveau d'exécution pour voir le rapport de blocage."
cancel-string: annuler
exit-string: annuler, sortir, quitter
write-search: "&e Veuillez entrer une valeur de recherche. (Ecrivez 'cancel' pour
quitter)"
search-updated: "&a Valeur de recherche mise à jour."
cancelled: "&c Conversation annulée !"
no-value: "&c Cet item n'a aucune valeur."
unknown-item: "&c Le '[material]' n'existe pas dans le jeu."
value: "&7 La valeur de '[material]' est : &e[value]"
value-underwater: "&7 La valeur de '[material]' sous le niveau de la mer : &e[value]"
empty-hand: "&c Il n'y a pas de blocs dans votre main"
meta: meta:
authors: authors:
'0': plagoutte - plagoutte

View File

@ -1,54 +1,39 @@
--- ###########################################################################################
# This is a YML file. Be careful when editing. Check your edits in a YAML checker like #
# the one at http://yaml-online-parser.appspot.com #
###########################################################################################
admin: admin:
level: level:
parameters: "<player>" parameters: "<player>"
description: Egy játékos sziget szintjének kiszámítása description: "Egy játékos sziget szintjének kiszámítása"
sethandicap:
parameters: "<játékos> <hátrány>"
description: állítsa be a sziget hátrányát, általában a kezdő sziget szintjét
changed: "&a A kezdeti sziget hátrány változott erről [number] erre [new_number]."
invalid-level: "&c Érvénytelen hátrány. Használj egész számot."
levelstatus:
description: megmutatja, hogy hány sziget van a szkennelési sorban
islands-in-queue: "&a Szigetek a sorban: [number]"
top: top:
description: Top Tíz lista megtekintése description: "Top Tíz lista megtekintése"
unknown-world: "&cIsmeretlen világ!" unknown-world: "&cIsmeretlen világ!"
display: "&f[rank]. &a[name] &7- &b[level]" display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: játékos törlése a Top Tízből
parameters: "<játékos>"
island: island:
level: level:
parameters: "[player]" parameters: "[player]"
description: A saját vagy más játékos sziget szintjének kiszámítása description: "A saját vagy más játékos sziget szintjének kiszámítása"
calculating: "&aSziget szint kiszámítása..." calculating: "&aSziget szint kiszámítása..."
estimated-wait: "&a Becsült várakozás: [number] másodperc"
in-queue: "&a Te vagy a(z) [number] a sorban"
island-level-is: "&aA sziget szint: &b[level]" island-level-is: "&aA sziget szint: &b[level]"
required-points-to-next-level: "&a[points] pont szükséges a következő szinthez." required-points-to-next-level: "&a[points] pont szükséges a következő szinthez."
deaths: "&c([number] halál)" deaths: "&c([number] halál)"
cooldown: "&cVárnod kell &b[time] &cmásodpercet, hogy újra használhasd." cooldown: "&cVárnod kell &b[time] &cmásodpercet, hogy újra használhasd."
top: top:
description: Top Tíz lista megtekintése description: "Top Tíz lista megtekintése"
gui-title: "&aTop Tíz" gui-title: "&aTop Tíz"
gui-heading: "&6[name]: &B[rank]" gui-heading: "&6[name]: &B[rank]"
island-level: "&BLevel [level]" island-level: "&BLevel [level]"
warp-to: "&ATeleportálás [name] szigetére." warp-to: "&ATeleportálás [name] szigetére."
remove: remove:
description: játékos törlése a Top Tízből description: "játékos törlése a Top Tízből"
parameters: "<player>" parameters: "<player>"
level-details:
above-sea-level-blocks: Tengerszint Feletti Blokkok
spawners: Spawner-ek
underwater-blocks: Víz Alatti Blokkok
all-blocks: Minden Blokk
no-island: "&c Nincs sziget!"
names-island: "[name] szigete"
syntax: "[name] x [number]"
hint: "&c Futtassa a szintet a blokk jelentés megjelenítéséhez"
value: value:
description: Bármely blokk értékét mutatja description: "Bármely blokk értékét mutatja"
success: "&7Ennek a blokknak az értéke: &e[value]" success: "&7Ennek a blokknak az értéke: &e[value]"
success-underwater: "&7Ennek a blokknak a tengerszint alatti értéke: &e[value]" success-underwater: "&7Ennek a blokknak a tengerszint alatti értéke: &e[value]"
empty-hand: "&cNincsenek blokkok a kezedben" empty-hand: "&cNincsenek blokkok a kezedben"

View File

@ -1,171 +1 @@
---
admin:
level:
parameters: "<player>"
description: hitung level pulau untuk pemain
sethandicap:
parameters: "<player> <handicap>"
description: mengatur handicap pulau, biasanya level pulau pemula
changed: "&a Handicap pulau awal diubah dari [number] menjadi [new_number]."
invalid-level: "&c Handicap tidak valid. Gunakan angka bulat."
levelstatus:
description: menunjukkan berapa banyak pulau dalam antrian untuk pemindaian
islands-in-queue: "&a Pulau di dalam antrian: [number]"
top:
description: menunjukkan daftar sepuluh besar
unknown-world: "&c Dunia tidak ditemukan!"
display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: menghapus pemain dari sepuluh besar
parameters: "<player>"
island:
level:
parameters: "[player]"
description: hitung level pulau kamu atau melihat level [player]
calculating: "&a Menghitung level..."
estimated-wait: "&a Perkiraan menunggu: [number] detik"
in-queue: "&a Kamu berada pada antrian nomor [number]"
island-level-is: "&a Level pulau adalah &b[level]"
required-points-to-next-level: "&a [points] poin dibutuhkan hingga level selanjutnya"
deaths: "&c([number] kematian)"
cooldown: "&c Kamu harus menunggu &b[time] &c detik sebelum kamu dapat melakukannya
lagi"
in-progress: "&6 Perhitungan level pulau sedang dijalankan..."
time-out: "&c Perhitungan level pulau terlalu lama. Coba lagi nanti."
top:
description: menunjukkan Sepuluh Besar
gui-title: "&a Sepuluh Besar"
gui-heading: "&6[name]: &B[rank]"
island-level: "&b Level [level]"
warp-to: "&A Warp ke pulau [name]"
level-details:
above-sea-level-blocks: Blok di atas permukaan laut
spawners: Spawner
underwater-blocks: Blok di bawah permukaan laut
all-blocks: Semua blok
no-island: "&c Tidak ada pulau!"
names-island: Pulau [name]
syntax: "[name] x [number]"
hint: "&c Jalankan perintah level untuk melihat laporan blok"
level:
commands:
value:
parameters: "[hand|<material>]"
description: menunjukkan nilai blok. Tambah 'hand' di akhir untuk menjukkan
nilai item di tangan.
gui:
titles:
top: "&0&l Pulau Terbaik"
detail-panel: "&0&l Pulau [name]"
value-panel: "&0&l Nilai Blok"
buttons:
island:
empty: "&f&l [name]. place"
name: "&f&l [name]"
description: |-
[owner]
[members]
[place]
[level]
owners-island: Pulau [player]
owner: "&7&l Pemilik: &r&b [player]"
members-title: "&7&l Anggota:"
member: "&b - [player]"
unknown: tidak diketahui
place: "&r&7Peringkat &7&o [number]."
level: "&7 Level: &o [number]"
material:
name: "&f&l [number] x [material]"
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: "&7 Id blok: &e [id]"
value: "&7 Nilai blok: &e [number]"
limit: "&7 Batas blok: &e [number]"
count: "&7 Jumlah blok: &e [number]"
calculated: "&7 Nilai yang dihitung: &e [number]"
all_blocks:
name: "&f&l Semua blok"
description: |-
&7 Tampilkan semua blok
&7 di pulau.
above_sea_level:
name: "&f&l Blok Diatas Permukaan Laut"
description: |-
&7 Hanya mengampilkan blok
&7 yang berada di atas
&7 permukaan laut.
underwater:
name: "&f&l Blok Di bawah Permukaan Laut"
description: |-
&7 Hanya menampilkan blok
&7 yang berada di bawah
&7 permukaan laut.
spawner:
name: "&f&l Spawner"
description: "&7 Hanya tampilkan spawner."
filters:
name:
name: "&f&l Urut berdasarkan Nama"
description: "&7 Mengurutkan semua blok berdasarkan nama."
value:
name: "&f&l Urut berdasarkan Nilai"
description: "&7 Mengurutkan semua blok berdasarkan nilainya."
count:
name: "&f&l Urut berdasarkan Jumlah"
description: "&7 Mengurutkan semua blok berdasarkan jumlahnya."
value:
name: "&f&l [material]"
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: "&7 Id blok: &e [id]"
value: "&7 Nilai blok: &e [number]"
underwater: "&7 Dibawah permukaan laut: &e [number]"
limit: "&7 Batas block: &e [number]"
previous:
name: "&f&l Halaman sebelumnya"
description: "&7 Beralih ke halaman [number]"
next:
name: "&f&l Halaman selanjutnya"
description: "&7 Beralih ke halaman [number]"
search:
name: "&f&l Cari"
description: |-
&7 Mencari nilai yang
&7 spesifik.
search: "&b Nilai: [value]"
tips:
click-to-view: "&e Klik &7 untuk melihat."
click-to-previous: "&e Klik &7 untuk melihat halaman sebelumnya."
click-to-next: "&e Klik &7 untuk melihat halaman selanjutnya."
click-to-select: "&e Klik &7 untuk memilih."
left-click-to-cycle-up: "&e Klik Kiri &7 untuk memutar ke atas."
right-click-to-cycle-down: "&e Klik Kanan &7 memutar ke bawah."
left-click-to-change: "&e Klik Kiri &7 untuk mengubah."
right-click-to-clear: "&e Klik Kanan &7 untuk membersihkan."
click-to-asc: "&e Klik &7 untuk mengurutkan dalam urutan menaik."
click-to-desc: "&e Klik &7 untuk mengurutkan dalam urutan menurun."
click-to-warp: "&e Klik &7 untuk warp."
click-to-visit: "&e Klik &7 untuk mengunjungi."
right-click-to-visit: "&e Klik Kanan &7 untuk mengunjungi."
conversations:
prefix: "&l&6 [BentoBox]: &r"
no-data: "&c Jalankan perintah level untuk melihat laporan blok"
cancel-string: batal
exit-string: batal, keluar, berhenti
write-search: "&e Tolong masukkan pencarian nilai. (Ketik 'batal' untuk keluar)"
search-updated: "&a Nilai pencarian diperbarui."
cancelled: "&c Percakapan dibatalkan!"
no-value: "&c Item itu tidak ada nilai."
unknown-item: "&c '[material]' tidak ada di dalam permainan."
value: "&7 Nilai dari '[material]' adalah: &e[value]"
value-underwater: "&7Nilai dari '[material]' di bawah permukaan laut: &e[value]"
empty-hand: "&c Tidak ada blok di tangan mu"

View File

@ -1,51 +0,0 @@
---
admin:
level:
parameters: "<player>"
description: 플레이어의 섬레벨을 계산합니다
sethandicap:
parameters: "<플레이어> <핸디캡>"
description: 섬 핸디캡을 설정하십시오. 일반적으로 시작 섬의 레벨
changed: "& a 초기 아일랜드 핸디캡이 [번호]에서 [new_number] (으)로 변경되었습니다."
invalid-level: "& c 잘못된 핸디캡. 정수를 사용하십시오."
levelstatus:
description: 스캔 대기열에 몇 개의 섬이 있는지 표시
islands-in-queue: "& a 대기열에있는 섬 : [번호]"
top:
unknown-world: "& c 알수없는 월드 입니다"
display: "&f[rank]. &a[name] &7-&b[level]"
remove:
description: 탑 10에서 플레이어를 제거합니다
parameters: "<플레이어>"
island:
level:
parameters: "[플레이어]"
description: 섬 레벨을 계산하거나 [플레이어]의 섬레벨을 보여줍니다
calculating: "&a 계산중....\n"
estimated-wait: "&a예상 대기 시간 : [번호] 초"
in-queue: "& a 당신은 대기열에있는 숫자 [번호]입니다"
island-level-is: "& a 섬 레벨은 & b [level]"
required-points-to-next-level: "&a [point] 다음 레벨까지 요구되는 경험치"
deaths: "&c ([number] 사망)"
cooldown: "&c그것을 다시하려면 &b[time]초&c를 기다려야합니다."
top:
description: 탑 10을 보여줍니다
gui-title: "&a 탑 10"
gui-heading: "&6 [name] : &B[rank]"
island-level: "&b 레벨 [level]"
warp-to: "&a[name]님의 섬으로 이동중입니다.."
level-details:
above-sea-level-blocks: 해발 블록
spawners: 스포너
underwater-blocks: 수중 블록
all-blocks: 모든 블록
no-island: "&c 섬이 없습니다."
names-island: "[name]의 섬"
syntax: "[name] x [number]"
hint: "&c 블록 리포트를 보려면 레벨을 해야합니다."
value:
description: 모든 블록의 값을 보여줍니다
success: "&7이 블록의 값은 &e [value]입니다."
success-underwater: "&7 해수면 아래의 블록 값 : &e [value]"
empty-hand: "& c 손에 블록이 없습니다"
no-value: "&c 해당 항목에는 가치가 없습니다."

View File

@ -1,165 +0,0 @@
---
admin:
level:
parameters: "<speler>"
description: bereken het eiland level voor een speler
sethandicap:
parameters: "<speler> <handicap>"
description: stel handicap in voor het eiland, normaal gesproken het level van
het starter eiland.
changed: "&a Initiële handicap is veranderd van [number] naar [new_number]."
invalid-level: "&c Ongeldige handicap. Gebruik een getal."
levelstatus:
description: laat zien hoeveel eilanden er in de wachtrij staan voor het scannen
islands-in-queue: "&a Aantal eilanden in de wachtrij: [number]"
top:
description: Laat de top tien zien
unknown-world: "&c Ongeldige wereld!"
display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: verwijder speler van de top tien
parameters: "<speler>"
island:
level:
parameters: "[speler]"
description: bereken het eiland level voor [player]
calculating: "&a Level aan het berekenen..."
estimated-wait: "&a Verwachtte wachttijd: [number] seconde"
in-queue: "&a Jij staat op plek [number] in de wachtrij"
island-level-is: "&a Eiland level is &b[level]"
required-points-to-next-level: "&a [points] punten nodig voor het volgende level"
deaths: "&c([number] doodgegaan)"
cooldown: "&c Je moet nog &b[time] &c seconden wachten tot je dit weer kan doen."
in-progress: "&6 Eiland level wordt berekend..."
time-out: "&c De level berekening duurde te lang. Probeer het later opnieuw."
top:
description: Toon de Top tien
gui-title: "&a Top tien"
gui-heading: "&6[name]: &B[rank]"
island-level: "&b Level [level]"
warp-to: "&A Teleporteren naar [name]'s eiland"
level-details:
above-sea-level-blocks: 'Blokken boven zeeniveau '
spawners: Monsterkooien
underwater-blocks: Blokken onder zeeniveau
all-blocks: Alle blokken
no-island: "&c Geen eiland!"
names-island: "[name]'s eiland"
syntax: "[name] x [number]"
hint: "&c Gebruik level om het blokkenrapport te zien"
level:
commands:
value:
parameters: "[hand|<type>]"
description: toont de waarde van blokken. Voeg 'hand' toe aan het einde om de
waarde te laten zien van het item in je hand.
gui:
titles:
top: "&0&l Top eilanden"
detail-panel: "&0&l [name]'s eiland"
value-panel: "&0&l Blok waardes"
buttons:
island:
empty: "&f&l [name]. plaats"
name: "&f&l [name]"
description: |-
[owner]
[members]
[place]
[level]
owners-island: "[player]'s Eiland"
owner: "&7&l Eigenaar: &r&b [player]"
members-title: "&7&l Leden:"
member: "&b - [player]"
unknown: onbekend
place: "&7&o [number]. &r&7 plaats"
level: "&7 Level: &o [number]"
material:
name: "&f&l [number] x [material]"
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: "&7 Blok id: &e [id]"
value: "&7 Block waarde: &e [number]"
limit: "&7 Block limiet: &e [number]"
count: "&7 Aantal blokken: &e [number]"
calculated: "&7 Berekende waarde: &e [number]"
all_blocks:
name: "&f&l Alle Blokken"
description: "&7 Toon alle blokken \n&7 op het eiland."
above_sea_level:
name: "&f&l Blokken boven zeeniveau"
description: |-
&7 Toon alleen blokken
&7 die boven zeeniveau zijn
underwater:
name: "&f&l Blokken onder zeeniveau"
description: |-
&7 Toon alleen blokken
&7 die onder zeeniveau zijn
spawner:
name: "&f&l Monsterkooien"
description: "&7 Toon alleen monsterkooien."
filters:
name:
name: "&f&l Sorteer aan de hand van naam"
description: "&7 Sorteer alle blokken aan de hand van naam."
value:
name: "&f&l Sorteer aan de hand van waarde"
description: "&7 Sorteer alle blokken aan de hand van waarde."
count:
name: "&f&l Sorteer aan de hand van aantal"
description: "&7 Sorteer alle blokken aan de hand van aantal."
value:
name: "&f&l [material]"
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: "&7 Blok id: &e [id]"
value: "&7 Block waarrde: &e [number]"
underwater: "&7 Onder zeeniveau: &e [number]"
limit: "&7 Blok limiet: &e [number]"
previous:
name: "&f&l Vorige pagina"
description: "&7 Ga naar pagina [number]"
next:
name: "&f&l Volgende pagina"
description: "&7 Ga naar pagina [number]"
search:
name: "&f&l Zoek"
description: "&7 Zoek voor een \n&7 specifieke waarde."
search: "&b Waarde: [value]"
tips:
click-to-view: "&e Klik &7 om te zien."
click-to-previous: "&e Klik &7 om de vorige pagina te zien."
click-to-next: "&e Klik &7 om de volgende pagina te zien."
click-to-select: "&e Klik &7 om te selecteren."
left-click-to-cycle-up: "&e Linker Klik &7 om door te lopen."
right-click-to-cycle-down: "&e Rechter Klik &7 om terug door te lopen."
left-click-to-change: "&e Linker Klik &7 om bij te werken."
right-click-to-clear: "&e Linker Klik &7 om te verwijderen."
click-to-asc: "&e Klik &7 om te toenemend te sorteren."
click-to-desc: "&e Klik &7 om te afnemenend te sorteren."
click-to-warp: "&e Klik &7 om te teleporteren."
click-to-visit: "&e Klik &7 om te bezoeken."
right-click-to-visit: "&e Rechter Klik &7 om te bezoeken."
conversations:
prefix: "&l&6 [BentoBox]: &r"
no-data: "&c Gebruik level om het blokkenrapport te zien."
cancel-string: stop
exit-string: stop
write-search: "&e Schrijf een zoekopdracht. (Schrijf 'stop' om te zoeken)"
search-updated: "&a Zoekopdracht bijgewerkt."
cancelled: "&c Conversatie gestopt!"
no-value: "&c Dit item heeft geen waarde."
unknown-item: "&c '[material]' bestaat niet in het spel."
value: "&7 De waarde van '[material]' is: &e[value]"
value-underwater: "&7 The waarde van '[material]' onder zeeniveau: &e[value]"
empty-hand: "&c Je hebt geen blok vast"

View File

@ -1,32 +1,29 @@
--- ---
admin: admin:
level: level:
parameters: "<player>" parameters: "<gracz>"
description: oblicza poziom wyspy description: oblicza poziom wyspy
sethandicap: sethandicap:
parameters: "<gracz> <uposledzenie>" parameters: "<gracz> <uposledzenie>"
description: ustawić 0 poziom wyspy, zwykle poziom wyspy startowej
changed: "&a Początkowy poziom wysp został zmieniony z [number] na [new_number]."
invalid-level: "&c Nieprawidłowy poziom. Użyj liczby całkowitej."
levelstatus: levelstatus:
description: pokazuje ile wysp znajduje się w kolejce do skanowania description: pokazuje ile wysp znajduje się w kolejce do skanowania
islands-in-queue: "&a Wyspy w kolejce: [number]" islands-in-queue: "&a Wyspy w kolejce: [numer]"
top: top:
description: pokazuje Top 10 wysp description: pokazuje Top 10 wysp
unknown-world: "&cNieznany świat!" unknown-world: "&cNieznany świat!"
display: "&f[rank]. &a[name] &7- &b[level]" display: "&f[ranking]. &a[nazwa-gracza] &7- &b[poziom]"
remove: remove:
description: usuwa gracza z Top 10 description: usuwa gracza z Top 10
parameters: "<gracz>" parameters: "<gracz>"
island: island:
level: level:
parameters: "[player]" parameters: "[gracz]"
description: oblicza poziom wyspy lub pokazać poziom [player] description: oblicza poziom wyspy lub pokazać poziom [gracza]
calculating: "&aObliczanie poziomu wyspy..." calculating: "&aObliczanie poziomu wyspy..."
estimated-wait: "&a Szacowany czas: [number] sekund" estimated-wait: "&a Szacowany czas: [numer] sekund"
in-queue: "&a Jestes numerem [number] w kolejce" in-queue: "&a Jestes numerem [numer] w kolejce"
island-level-is: "&aPoziom wyspy wynosi &b[level]" island-level-is: "&aPoziom wyspy wynosi &b[level]"
required-points-to-next-level: "&aPozostało [points] punktów do następnego poziomu" required-points-to-next-level: "&a[points] punktów do następnego poziomu"
deaths: "&c([number] śmierci)" deaths: "&c([number] śmierci)"
cooldown: "&cMusisz zaczekać &b[time] &csekund przed następnym obliczeniem poziomu" cooldown: "&cMusisz zaczekać &b[time] &csekund przed następnym obliczeniem poziomu"
in-progress: "&6 Trwa obliczanie poziomu twojej wyspy..." in-progress: "&6 Trwa obliczanie poziomu twojej wyspy..."
@ -44,128 +41,12 @@ island:
underwater-blocks: Podwodne bloki underwater-blocks: Podwodne bloki
all-blocks: Wszystkie bloki all-blocks: Wszystkie bloki
no-island: "&c Brak wyspy!" no-island: "&c Brak wyspy!"
names-island: Wyspa gracza [name] names-island: Wyspa gracza [nazwa]
syntax: "[name] x [number]" syntax: "[nazwa] x [numer]"
hint: "&c Uruchom poziom, aby wyświetlić raport o blokach" hint: "&c Uruchom poziom, aby wyświetlić raport o blokach"
level:
commands:
value: value:
parameters: "[hand|<materiał>]" description: pokazuje wartość dowolnego przedmiotu
description: pokazuje wartość bloków. Dodaj „hand” na końcu, aby wyświetlić success: "&7Wartość punktowa tego bloku wynosi: &e[value]"
wartość pozycji w ręku. success-underwater: "&7Wartość tego bloku poniżej poziomu morza: &e[value]"
gui: empty-hand: "&cNie trzymasz żadnego bloku."
titles: no-value: "&cTen przedmiot nie ma wartości :("
top: "&0&l Najlepsze wyspy"
detail-panel: "&0&l Wyspa gracza [name] "
value-panel: "&0&l Wartości bloków"
buttons:
island:
empty: "&f&l [name]. miejsce"
name: "&f&l [name]"
description: |-
[owner]
[members]
[place]
[level]
owners-island: wyspa gracza [player]
owner: "&7&l Lider: &r&b [player]"
members-title: "&7&l Członkowie:"
member: "&b - [player]"
unknown: nieznany
place: "&7&o [number]. &r&7 miejsce"
level: "&7 Poziom: &o [number]"
material:
name: "&f&l [number] x [material]"
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: "&7 Identyfikator bloku: &e [id]"
value: "&7 Wartość bloku: &e [number]"
limit: "&7 Limit bloków: &e [number]"
count: "&7 Numer bloku: &e [number]"
calculated: "&7 Obliczona wartość: &e [number]"
all_blocks:
name: "&f&l Wszystkie bloki"
description: |-
&7 Wyświetl wszystkie bloki
&7 na wyspie.
above_sea_level:
name: "&f&l Bloki nad poziomem morza"
description: |-
&7 Wyświetlaj tylko bloki
&7 które są nad poziomem
&7 morza
underwater:
name: "&f&l Bloki pod poziomem morza"
description: |-
&7 Wyświetlaj tylko bloki
&7 ponad poziomem morza
spawner:
name: "&f&l Spawnery"
description: "&7 Wyświetlaj tylko spawnery."
filters:
name:
name: "&f&l Sortuj według nazwy"
description: "&7 Sortuj wszystkie bloki według nazwy."
value:
name: "&f&l Sortuj według wartości"
description: "&7 Sortuj wszystkie bloki według ich wartości."
count:
name: "&f&l Sortuj według liczby"
description: "&7 Sortuj wszystkie bloki według ich ilości."
value:
name: "&f&l [material]"
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: "&7 Identyfikator bloku: &e [id]"
value: "&7 Wartość bloku: &e [number]"
underwater: "&7 Poniżej poziomu morza: &e [number]"
limit: "&7 Limit bloku: &e [number]"
previous:
name: "&f&l Poprzednia strona"
description: "&7 Przełącz na stronę [number]"
next:
name: "&f&l Następna strona"
description: "&7 Przełącz na stronę [number]"
search:
name: "&f&l Szukaj"
description: |-
&7 Wyszukaj konkretną
&7 wartość.
search: "&b Wartość: [value]"
tips:
click-to-view: "&e Kliknij &7, aby wyświetlić."
click-to-previous: "&e Kliknij &7, aby wyświetlić poprzednią stronę."
click-to-next: "&e Kliknij &7, aby wyświetlić następną stronę."
click-to-select: "&e Kliknij &7, aby wybrać."
left-click-to-cycle-up: "&e Kliknij lewym przyciskiem &7, aby przejść w górę."
right-click-to-cycle-down: "&e Kliknij prawym przyciskiem &7, aby przejść w
dół."
left-click-to-change: "&e Kliknij lewym przyciskiem &7, aby edytować."
right-click-to-clear: "&e Kliknij prawym przyciskiem &7, aby wyczyścić."
click-to-asc: "&e Kliknij &7, aby posortować w porządku rosnącym."
click-to-desc: "&e Kliknij &7, aby posortować w porządku malejącym."
click-to-warp: "&e Kliknij&7, aby przenieść"
click-to-visit: "&e Kliknij&7, aby odwiedzić"
right-click-to-visit: "&e Kliknij prawym przyciskiem &7, aby odwiedzić."
conversations:
prefix: "&l&6 [BentoBox]: &r"
no-data: "&c Wykonaj sprawdzenie poziomu, przed raportem bloków"
cancel-string: anuluj
exit-string: cancel, exit, quit, anuluj
write-search: "&e Wprowadź wartość wyszukiwania. (Napisz „anuluj”, aby wyjść)"
search-updated: "&a Zaktualizowano wartość wyszukiwania."
cancelled: "&c Rozmowa została anulowana!"
no-value: "&c Ten element nie ma wartości."
unknown-item: "&c „[material]” nie istnieje w grze."
value: "&7 Wartość '[material]' to: &e[value]"
value-underwater: "&7 Wartość „[material]” poniżej poziomu morza: &e[value]"
empty-hand: "&c W twojej ręce nie ma bloków"

View File

@ -0,0 +1 @@

View File

@ -1,184 +0,0 @@
---
admin:
level:
parameters: "<player>"
description: розрахувати рівень острова для гравця
sethandicap:
parameters: "<player> <handicap>"
description: встановити гандикап острова, як правило, рівень острова стартера
changed: "&a Початковий гандикап острова змінено з [number] на [new_number]."
invalid-level: "&c Недійсний гандикап. Використовуйте ціле число."
levelstatus:
description: показати, скільки островів у черзі на сканування
islands-in-queue: "&a Острови в черзі: [number]"
top:
description: показати першу десятку списку
unknown-world: "&c Невідомий світ!"
display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: видалити гравця з першої десятки
parameters: "<player>"
stats:
description: показати статистику островів на цьому сервері
title: Статистика острова сервера
world: "&a [name]"
no-data: "&c Немає даних для обробки."
average-level: 'Середній рівень острова: [number]'
median-level: 'Середній рівень острова: [number]'
mode-level: 'Рівень острова режиму: [number]'
highest-level: 'Найвищий рівень острова: [number]'
lowest-level: 'Найнижчий рівень острова: [number]'
distribution: 'Розподіл на рівні острова:'
islands: острови
island:
level:
parameters: "[player]"
description: обчисліть свій рівень острова або покажіть рівень [player]
calculating: "&a Розрахунок рівня..."
estimated-wait: "&a Приблизне очікування: [number] секунд"
in-queue: "&a Ви номер [number] у черзі"
island-level-is: "&a Рівень острова &b[level]"
required-points-to-next-level: "&a [points] потрібні бали до наступного рівня"
deaths: "&c([number] смерті)"
cooldown: "&c Ви повинні зачекати &b[time] &c секунд, поки ви зможете зробити
це знову"
in-progress: "&6 Розрахунок рівня острова триває..."
time-out: "&c Розрахунок рівня тривав занадто довго. Будь-ласка спробуйте пізніше."
top:
description: показати першу десятку
gui-title: "& Десятка Кращих"
gui-heading: "&6[name]: &B[rank]"
island-level: "&b Рівень [level]"
warp-to: "&A Варп на острів [name]."
level-details:
above-sea-level-blocks: Блоки над рівнем моря
spawners: Спавера
underwater-blocks: Підводні блоки
all-blocks: Всі блоки
no-island: "&c Немає острова!"
names-island: острів [name].
syntax: "[name] x [number]"
hint: "&c Запустіть рівень, щоб переглянути звіт про блокування"
level:
commands:
value:
parameters: "[hand|<material>]"
description: показує значення блоків. Додайте 'hand' в кінці, щоб відобразити
значення предмета в руках.
gui:
titles:
top: "&0&l Топ островів"
detail-panel: "&0&l острів [name]."
value-panel: "&0&l Значення блоку"
buttons:
island:
empty: "&f&l [name]. місце"
name: "&f&l [name]"
description: |-
[owner]
[members]
[place]
[level]
owners-island: Острів [player].
owner: "&7&l Власник: &r&b [player]"
members-title: "&7&l Члени:"
member: "&b - [player]"
unknown: невідомий
place: "&7&o [number]. &r&7 місце"
level: "&7 Рівень: &o [number]"
material:
name: "&f&l [number] x [material]"
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: "&7 Ідентифікатор блоку: &e [id]"
value: "&7 Значення блоку: &e [number]"
limit: "&7 Обмеження блоку: &e [number]"
count: "&7 Кількість блоків: &e [number]"
calculated: "&7 Розраховане значення: &e [number]"
all_blocks:
name: "&f&l Усі блоки"
description: |-
&7 Показати всі блоки
&7 на острові.
above_sea_level:
name: "&f&l Блоки над рівнем моря"
description: |-
&7 Показувати лише блоки
&7, які знаходяться над морем
&7 рівень.
underwater:
name: "&f&l Блоки під рівнем моря"
description: |-
&7 Показувати лише блоки
&7, які знаходяться нижче моря
&7 рівень.
spawner:
name: "&f&l Спанера"
description: "&7 Відображати лише спавнери."
filters:
name:
name: "&f&l Сортувати за назвою"
description: "&7 Сортувати всі блоки за назвою."
value:
name: "&f&l Сортувати за значенням"
description: "&7 Сортувати всі блоки за їх значенням."
count:
name: "&f&l Сортувати за кількістю"
description: "&7 Відсортуйте всі блоки за їх кількістю."
value:
name: "&f&l [material]"
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: "&7 Ідентифікатор блоку: &e [id]"
value: "&7 Значення блоку: &e [number]"
underwater: "&7 Нижче рівня моря: &e [number]"
limit: "&7 Обмеження блоку: &e [number]"
previous:
name: "&f&l Попередня сторінка"
description: "&7 Перейти на сторінку [number]."
next:
name: "&f&l Наступна сторінка"
description: "&7 Перейти на сторінку [number]."
search:
name: "&f&l Пошук"
description: |-
&7 Пошук конкретного
&7 значення.
search: "&b Значення: [value]"
tips:
click-to-view: "&e Натисніть &7, щоб переглянути."
click-to-previous: "&e Натисніть &7, щоб переглянути попередню сторінку."
click-to-next: "&e Натисніть &7, щоб переглянути наступну сторінку."
click-to-select: "&e Натисніть &7, щоб вибрати."
left-click-to-cycle-up: "&e Клацніть лівою кнопкою миші &7, щоб перейти вгору."
right-click-to-cycle-down: "&e Клацніть правою кнопкою миші &7, щоб перейти
вниз."
left-click-to-change: "&e Клацніть лівою кнопкою миші &7 для редагування."
right-click-to-clear: "&e Клацніть правою кнопкою миші &7, щоб очистити."
click-to-asc: "&e Клацніть &7, щоб відсортувати в порядку збільшення."
click-to-desc: "&e Клацніть &7, щоб відсортувати в порядку зменшення."
click-to-warp: "&e Натисніть &7, щоб деформувати."
click-to-visit: "&e Натисніть &7, щоб відвідати."
right-click-to-visit: "&e Клацніть правою кнопкою миші &7, щоб відвідати."
conversations:
prefix: "&l&6 [BentoBox]: &r"
no-data: "&c Запустіть рівень, щоб переглянути звіт про блокування."
cancel-string: cancel
exit-string: cancel, exit, quit
write-search: "&e Введіть пошукове значення. (Напишіть 'cancel', щоб вийти)"
search-updated: "&a Значення пошуку оновлено."
cancelled: "&c Розмова скасована!"
no-value: "&c Цей предмет не має цінності."
unknown-item: "&c '[material]' не існує в грі."
value: "&7 Значення '[material]' таке: &e[value]"
value-underwater: "&7 Значення '[material]' нижче рівня моря: &e[value]"
empty-hand: "&c У вашій руці немає блоків"

View File

@ -1,56 +0,0 @@
#
# This is a YML file. Be careful when editing. Check your edits in a YAML checker like #
# the one at http://yaml-online-parser.appspot.com #
admin:
level:
parameters: <người chơi>
description: tính toán cấp độ đảo của người chơi
sethandicap:
parameters: <người chơi> <cấp>
description: chỉnh cấp của các đảo bắt đầu
changed: '&a Cấp đảo bắt đầu đã chỉnh từ [number] thành [new_number].'
invalid-level: '&c Cấp không xác định. Hãy dùng số nguyên.'
levelstatus:
description: xem bao nhiêu đảo đang trong hàng chờ được quét
islands-in-queue: '&a Đảo đang chờ: [number]'
top:
description: xem bảng xếp hạng TOP 10
unknown-world: '&c Thế giới không xác định!'
display: '&f[rank]. &a[name] &7- &b[level]'
remove:
description: xoá người khỏi TOP 10
parameters: <người chơi>
island:
level:
parameters: '[người chơi]'
description: tính toán cấp đảo của bạn hoặc xem cấp đảo của [người chơi]
calculating: '&a Đang tính toán cấp đảo...'
estimated-wait: '&a Thời gian còn lại: [number] giây'
in-queue: '&a Bạn đang ở vị trí [number] trong hàng chờ'
island-level-is: '&a Cấp đảo là &b[level]'
required-points-to-next-level: '&a Cần [points] điểm để qua cấp tiếp theo'
deaths: '&c([number] lần chết)'
cooldown: '&c Bạn phải chờ &b[time] &c giây trước khi có thể làm điều đó'
in-progress: '&6 Quá trình tính toán cấp đảo đang thực hiện...'
time-out: '&c Tính toán cấp đảo quá lâu. Vui lòng thử lại sau.'
top:
description: xem TOP 10
gui-title: '&a TOP 10'
gui-heading: '&6[name]: &B[rank]'
island-level: '&b Cấp [level]'
warp-to: '&A Đang dịch chuyển đến đảo của [name]'
level-details:
above-sea-level-blocks: Khối Trên Mực Nước Biển
spawners: Lồng Sinh Quái
underwater-blocks: Khối Dưới Nước
all-blocks: Toàn Bộ Khối
no-island: '&c Không có đảo!'
names-island: 'đảo của [name]'
syntax: '[name] x [number]'
hint: '&c Chạy lệnh cấp để xem báo cáo khối'
value:
description: xem giá trị của bất kì khối
success: '&7 Giá trị của khối này là: &e[value]'
success-underwater: '&7 Giá trị của khối này dưới mực nước biển: &e[value]'
empty-hand: '&c Không có khối nào trên tay bạn'
no-value: '&c Vật phẩm này vô giá trị.'

193
src/main/resources/locales/zh-CN.yml Normal file → Executable file
View File

@ -1,173 +1,40 @@
###########################################################################################
# This is a YML file. Be careful when editing. Check your edits in a YAML checker like #
# the one at http://yaml-online-parser.appspot.com #
###########################################################################################
admin: admin:
level: level:
parameters: <player> parameters: "<player>"
description: 计算指定玩家的岛屿等级 description: "计算某玩家的岛屿等级"
sethandicap:
parameters: <player> <handicap>
description: 设置偏差值, 通常用于抵消初始岛屿等级, 来保证岛屿等级从零开始. 实际岛屿等级 - <handicap> = 最终的岛屿等级
changed: '&a岛屿的偏差值从[number]更改为[new_number]'
invalid-level: '&c偏差值无效, 请使用整数'
levelstatus:
description: 显示等级计算队列中的岛屿
islands-in-queue: '&a列队中的岛屿: [number]'
top: top:
description: 显示前十名 description: "显示前十名"
unknown-world: '&c未知的世界!' unknown-world: "&c未知世界!"
display: '&f[rank]. &a[name] &7- &b[level]' display: "&f[rank]. &a[name] &7- &b[level]"
remove: remove:
description: 将玩家移出前十名 description: "将玩家移出前十"
parameters: <player> parameters: "<player>"
stats:
description: 显示该服务器上岛屿的统计数据
title: 服务器岛屿数据
world: '&a[name]'
no-data: '&c没有数据.'
average-level: '平均岛屿等级: [number]'
median-level: '中位数岛屿等级: [number]'
mode-level: '众数岛屿等级: [number]'
highest-level: '最高岛屿等级: [number]'
lowest-level: '最低岛屿等级: [number]'
distribution: '岛屿等级分布:'
islands: 个岛屿
island: island:
level: level:
parameters: '[player]' parameters: "[player]"
description: 计算你或指定玩家[player]的岛屿等级 description: "计算你或玩家 [player] 的岛屿等级"
calculating: '&a等级计算中...' calculating: "&a计算等级中..."
estimated-wait: '&a预计等待时间: [number]秒' island-level-is: "&a岛屿等级为 &b[level]"
in-queue: '&a你处于队列中第[number]个' required-points-to-next-level: "&a还需 [points] 才能升到下一级"
island-level-is: '&a岛屿等级为: &b[level]' deaths: "&c([number] 次死亡)"
required-points-to-next-level: '&a还需[points]点数才能到达下一级' cooldown: "&c再等 &b[time] &c秒才能再次使用"
deaths: '&c([number]次死亡)'
cooldown: '&c还需等待&b[time]&c秒才能再次使用该指令'
in-progress: '&6岛屿等级正在计算中...'
time-out: '&c等级计算超时, 请稍后再试'
top: top:
description: 显示前十名 description: "显示前十名"
gui-title: '&a前十' gui-title: "&a前十"
gui-heading: '&6[name]: &B[rank]' gui-heading: "&6[name]: &B[rank]"
island-level: '&b等级: [level]' island-level: "&B等级 [level]"
warp-to: '&a正在传送到[name]的岛屿' warp-to: "&A正传送到 [name] 的岛屿"
level-details:
above-sea-level-blocks: 海平面以上的方块
spawners: 刷怪笼
underwater-blocks: 水下的方块
all-blocks: 所有方块
no-island: '&c没有岛屿!'
names-island: '[name]的岛屿'
syntax: '[name] x [number]'
hint: '&c运行level指令查看方块报告'
level:
commands:
value: value:
parameters: '[hand|<material>]' description: "查看某方块的价值"
description: 显示方块的价值. 在末尾添加'hand'可显示手中方块的价值 success: "&7本方块的价值: &e[value]"
gui: success-underwater: "&7本方块的水下价值: &e[value]"
titles: empty-hand: "&c你手里没有方块"
top: '&0&l岛屿排行榜' no-value: "&c这个东西一文不值."
detail-panel: '&0&l[name]的岛屿'
value-panel: '&0&l方块价值'
buttons:
island:
empty: '&f&l第[name]名'
name: '&f&l[name]'
description: |-
[owner]
[members]
[place]
[level]
owners-island: '[player]的岛屿'
owner: '&7&l岛主: &r&b[player]'
members-title: '&7&l成员: '
member: '&b- [player]'
unknown: 未知
place: '&7第&7&o[number]&r&7名'
level: '&7等级: &o[number]'
material:
name: '&f&l [number] x [material]'
description: |-
[description]
[count]
[value]
[calculated]
[limit]
[id]
id: '&7方块ID: &e[id]'
value: '&7方块价值: &e[number]'
limit: '&7方块限制: &e[number]'
count: '&7方块数量: &e[number]'
calculated: '&7计算值: &e[number]'
all_blocks:
name: '&f&l所有方块'
description: '&7显示岛屿上所有的方块'
above_sea_level:
name: '&f&l海平面以上的方块'
description: '&7只显示所有海平面以上的方块'
underwater:
name: '&f&l海平面以下的方块'
description: 只显示所有海平面以下的方块
spawner:
name: '&f&l刷怪笼'
description: '&7只显示刷怪笼'
filters:
name:
name: '&f&l按名称排序'
description: '&7通过名称排序所有的方块'
value:
name: '&f&l按价值排序'
description: '&7通过价值排序所有的方块'
count:
name: '&f&l按数量排序'
description: '&7通过数量排序所有方块'
value:
name: '&f&l[material]'
description: |-
[description]
[value]
[underwater]
[limit]
[id]
id: '&7方块ID: &e[id]'
value: '&7方块价值: &e[number]'
underwater: '&7海平面以下方块的价值: &e[number]'
limit: '&7方块限制: &e[number]'
previous:
name: '&f&l上一页'
description: '&7切换到第[number]页'
next:
name: '&f&l下一页'
description: '&7切换到第[number]页'
search:
name: '&f&l搜索'
description: '&7搜索特定的内容'
search: '&b搜索值: [value]'
tips:
click-to-view: '&e点击 &7查看'
click-to-previous: '&e点击 &7查看上一页'
click-to-next: '&e点击 &7查看下一页'
click-to-select: '&e点击 &7选择'
left-click-to-cycle-up: '&e左键 &7向上循环'
right-click-to-cycle-down: '&e右键 &7向下循环'
left-click-to-change: '&e左键 &7编辑'
right-click-to-clear: '&e右键 &7清除'
click-to-asc: '&e点击 &7以升序排序'
click-to-desc: '&e点击 &7以降序排序'
click-to-warp: '&e点击 &7去岛屿传送点'
click-to-visit: '&e点击 &7参观'
right-click-to-visit: '&e右键 &7查看'
conversations:
prefix: '&l&6[BentoBox]: &r'
no-data: '&c运行level指令查看方块报告'
cancel-string: cancel
exit-string: cancel, exit, quit
write-search: '&e请输入要搜索的值. (输入''cancel''退出)'
search-updated: '&a搜索值已更新'
cancelled: '&c对话已取消'
no-value: '&c这件物品一文不值'
unknown-item: '&c物品''[material]''在游戏中不存在'
value: '&7物品''[material]''的价值: &e[value]'
value-underwater: '&7物品''[material]''在海平面以下的价值: &e[value]'
empty-hand: '&c你的手中没有拿着方块'

View File

@ -1,170 +0,0 @@
# Name of panel used for indentification in the code
detail_panel:
# Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file
title: level.gui.titles.detail-panel
# The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and
# the others refer to the inventories shown for those items.
type: INVENTORY
# The background of the panel. These items will be shown if other items are not there. STAINED_GLASS_PANEs give a good effect.
background:
icon: BLACK_STAINED_GLASS_PANE
# Each item may have text applied to it, but usually for background items, nothing is shown.
title: "&b&r" # Empty text. This is using the Bukkit chat color coding with &'s.
border:
# The border of each panel may be shown as a different item.
# It can be used to provide a contrast to items in the panel.
icon: BLACK_STAINED_GLASS_PANE
title: "&b&r" # Empty text
# This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders.
# This can be a list and rows must be between 1 and 6, if used.
force-shown: []
# The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
content:
# Row number
1:
# Column number
2:
# Icon is a Bukkit Material.
icon: STONE
# Title of the button shown to the user. This is a reference and the reference will be translatable in the locale file
title: level.gui.buttons.all_blocks.name
# Description of the button shown to the user in the lore. This is a reference and the reference will be translatable in the locale file
description: level.gui.buttons.all_blocks.description
# The data section is a key-value list of data relavent for this button. It is interpreted by the code implemented the panel.
# The convention is to specify the type and the panel tab that will open if pressed. These are Enums in the code.
data:
# Type button will go to the ALL_BLOCKS tab when clicked.
type: TAB
tab: ALL_BLOCKS
# Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
# click-types.
actions:
# Each action has an arbitrary descriptive name to define it.
view:
# The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
click-type: unknown
# tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
tooltip: level.gui.tips.click-to-view
3:
icon: GRASS_BLOCK
title: level.gui.buttons.above_sea_level.name
description: level.gui.buttons.above_sea_level.description
data:
type: TAB
tab: ABOVE_SEA_LEVEL
actions:
view:
click-type: unknown
tooltip: level.gui.tips.click-to-view
4:
icon: WATER_BUCKET
title: level.gui.buttons.underwater.name
description: level.gui.buttons.underwater.description
data:
type: TAB
tab: UNDERWATER
actions:
view:
click-type: unknown
tooltip: level.gui.tips.click-to-view
5:
icon: SPAWNER
title: level.gui.buttons.spawner.name
description: level.gui.buttons.spawner.description
data:
type: TAB
tab: SPAWNER
actions:
view:
click-type: unknown
tooltip: level.gui.tips.click-to-view
9:
# You can create multiple buttons. By default it is one.
icon: IRON_TRAPDOOR
# [filter] is a placeholder for different filter types. It will be replaced with name, value, count.
title: level.gui.buttons.filters.[filter].name
description: level.gui.buttons.filters.[filter].description
data:
type: FILTER
# the value of filter button. Suggestion is to leave first value to name if you use single button.
filter: NAME
actions:
up:
click-type: left
tooltip: level.gui.tips.left-click-to-cycle-up
down:
click-type: right
tooltip: level.gui.tips.right-click-to-cycle-down
# There is also select action. With it you can create multiple filter buttons.
# select:
# click-type: unknown
# tooltip: level.gui.tips.click-to-select
2:
# If a button is used repeatedly then it can be mentioned by name and then defined in the 'reusable' section
2: material_button
3: material_button
4: material_button
5: material_button
6: material_button
7: material_button
8: material_button
3:
1:
# In this case, the icon is defined as a TIPPED_ARROW with and enchantment applied. The format for the enchantment is
# define in {@link world.bentobox.bentobox.util.ItemParser} and available for POTIONS or TIPPED_ARROWs.
# Format TIPPED_ARROW:NAME:<LEVEL>:<EXTENDED>:<SPLASH/LINGER>:QTY
# LEVEL, EXTENDED, SPLASH, LINGER are optional.
# LEVEL is a number, 1 or 2
# LINGER is for V1.9 servers and later
# Examples:
# TIPPED_ARROW:STRENGTH:1:EXTENDED:SPLASH:1
# TIPPED_ARROW:INSTANT_DAMAGE:2::LINGER:2
# TIPPED_ARROW:JUMP:2:NOTEXTENDED:NOSPLASH:1
# TIPPED_ARROW:WEAKNESS::::1 - any weakness enchantment
icon: tipped_arrow{CustomPotionColor:11546150}
title: level.gui.buttons.previous.name
description: level.gui.buttons.previous.description
data:
type: PREVIOUS
indexing: true
actions:
previous:
click-type: unknown
tooltip: level.gui.tips.click-to-previous
2: material_button
3: material_button
4: material_button
5: material_button
6: material_button
7: material_button
8: material_button
9:
icon: tipped_arrow{CustomPotionColor:8439583}
title: level.gui.buttons.next.name
description: level.gui.buttons.next.description
data:
type: NEXT
indexing: true
actions:
next:
click-type: unknown
tooltip: level.gui.tips.click-to-next
4:
2: material_button
3: material_button
4: material_button
5: material_button
6: material_button
7: material_button
8: material_button
# This is where reuable buttons are defined.
reusable:
# This is the name of the button that is referenced
material_button:
# If the icon for a button is not defined, it defaults to AIR and so effectively will not be shown.
# icons are usually not defined if the icon is going to be dynamically set in the panel, e.g. in this case the material will vary
#icon: STONE
title: level.gui.buttons.material.name
description: level.gui.buttons.material.description
data:
type: BLOCK

View File

@ -1,208 +0,0 @@
# Name of panel used for indentification in the code
top_panel:
# Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file
title: level.gui.titles.top
# The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and
# the others refer to the inventories shown for those items.
type: INVENTORY
# The background of the panel. These items will be shown if other items are not there. STAINED_GLASS_PANEs give a good effect.
background:
icon: BLACK_STAINED_GLASS_PANE
# Each item may have text applied to it, but usually for background items, nothing is shown.
title: "&b&r" # Empty text
border:
# The border of each panel may be shown as a different item.
# It can be used to provide a contrast to items in the panel.
icon: BLACK_STAINED_GLASS_PANE
title: "&b&r" # Empty text
# This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders.
# This can be a list and rows must be between 1 and 6, if used.
force-shown: [2,3,4,5]
# The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
content:
# Row number
2:
# Column number
5:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 1
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
3:
4:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 2
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
6:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 3
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
4:
2:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 4
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
3:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 5
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
4:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 6
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
5:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 7
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
6:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 8
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
7:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 9
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
8:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: TOP
index: 10
actions:
warp:
click-type: LEFT
tooltip: level.gui.tips.click-to-warp
visit:
click-type: RIGHT
tooltip: level.gui.tips.right-click-to-visit
fallback:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
6:
5:
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
type: VIEW
actions:
view:
click-type: unknown
tooltip: level.gui.tips.click-to-view

View File

@ -1,109 +0,0 @@
value_panel:
title: level.gui.titles.value-panel
type: INVENTORY
background:
icon: BLACK_STAINED_GLASS_PANE
title: "&b&r" # Empty text
border:
icon: BLACK_STAINED_GLASS_PANE
title: "&b&r" # Empty text
force-shown: []
content:
1:
4:
icon: PAPER
title: level.gui.buttons.filters.name.name
description: level.gui.buttons.filters.name.description
data:
type: FILTER
# the value of filter button. Suggestion is to leave fist value to name if you use single button.
filter: NAME
actions:
asc:
click-type: unknown
tooltip: level.gui.tips.click-to-asc
desc:
click-type: unknown
tooltip: level.gui.tips.click-to-desc
5:
# You can create multiple buttons. By default it is one.
icon: MAP
title: level.gui.buttons.search.name
description: level.gui.buttons.search.description
data:
type: SEARCH
actions:
input:
click-type: left
tooltip: level.gui.tips.left-click-to-change
clear:
click-type: right
tooltip: level.gui.tips.right-click-to-clear
6:
icon: DIAMOND
title: level.gui.buttons.filters.value.name
description: level.gui.buttons.filters.value.description
data:
type: FILTER
# the value of filter button. Suggestion is to leave fist value to name if you use single button.
filter: VALUE
actions:
asc:
click-type: unknown
tooltip: level.gui.tips.click-to-asc
desc:
click-type: unknown
tooltip: level.gui.tips.click-to-desc
2:
2: material_button
3: material_button
4: material_button
5: material_button
6: material_button
7: material_button
8: material_button
3:
1:
icon: tipped_arrow{CustomPotionColor:11546150}
title: level.gui.buttons.previous.name
description: level.gui.buttons.previous.description
data:
type: PREVIOUS
indexing: true
actions:
previous:
click-type: unknown
tooltip: level.gui.tips.click-to-previous
2: material_button
3: material_button
4: material_button
5: material_button
6: material_button
7: material_button
8: material_button
9:
icon: tipped_arrow{CustomPotionColor:8439583}
title: level.gui.buttons.next.name
description: level.gui.buttons.next.description
data:
type: NEXT
indexing: true
actions:
next:
click-type: unknown
tooltip: level.gui.tips.click-to-next
4:
2: material_button
3: material_button
4: material_button
5: material_button
6: material_button
7: material_button
8: material_button
reusable:
material_button:
#icon: STONE
title: level.gui.buttons.value.name
description: level.gui.buttons.value.description
data:
type: BLOCK

View File

@ -1,9 +0,0 @@
name: BentoBox-Level
main: world.bentobox.level.LevelPladdon
version: ${project.version}${build.number}
api-version: "1.19"
authors: [tastybento]
contributors: ["The BentoBoxWorld Community"]
website: https://bentobox.world
description: ${project.description}

View File

@ -61,7 +61,6 @@ import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlaceholdersManager; import world.bentobox.bentobox.managers.PlaceholdersManager;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.config.BlockConfig; import world.bentobox.level.config.BlockConfig;
import world.bentobox.level.config.ConfigSettings; import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.IslandActivitiesListeners;
@ -73,7 +72,7 @@ import world.bentobox.level.listeners.JoinLeaveListener;
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@RunWith(PowerMockRunner.class) @RunWith(PowerMockRunner.class)
@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class }) @PrepareForTest({Bukkit.class, BentoBox.class, User.class})
public class LevelTest { public class LevelTest {
private static File jFile; private static File jFile;
@ -175,14 +174,15 @@ public class LevelTest {
IslandWorldManager iwm = mock(IslandWorldManager.class); IslandWorldManager iwm = mock(IslandWorldManager.class);
when(plugin.getIWM()).thenReturn(iwm); when(plugin.getIWM()).thenReturn(iwm);
// Player has island to begin with // Player has island to begin with
when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island);
when(plugin.getIslands()).thenReturn(im); when(plugin.getIslands()).thenReturn(im);
// Locales // Locales
// Return the reference (USE THIS IN THE FUTURE) // Return the reference (USE THIS IN THE FUTURE)
when(user.getTranslation(Mockito.anyString())) when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
.thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
// Server // Server
PowerMockito.mockStatic(Bukkit.class); PowerMockito.mockStatic(Bukkit.class);
@ -190,27 +190,20 @@ public class LevelTest {
when(Bukkit.getServer()).thenReturn(server); when(Bukkit.getServer()).thenReturn(server);
when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class));
when(Bukkit.getBukkitVersion()).thenReturn("");
// Util
PowerMockito.mockStatic(Util.class, Mockito.RETURNS_MOCKS);
when(Util.inTest()).thenReturn(true);
// Addon // Addon
addon = new Level(); addon = new Level();
File dataFolder = new File("addons/Level"); File dataFolder = new File("addons/Level");
addon.setDataFolder(dataFolder); addon.setDataFolder(dataFolder);
addon.setFile(jFile); addon.setFile(jFile);
AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test") AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test").authors("tastybento").build();
.authors("tastybento").build();
addon.setDescription(desc); addon.setDescription(desc);
addon.setSettings(new ConfigSettings()); addon.setSettings(new ConfigSettings());
// Addons manager // Addons manager
when(plugin.getAddonsManager()).thenReturn(am); when(plugin.getAddonsManager()).thenReturn(am);
// One game mode // One game mode
when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode)); when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode));
AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test") AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build();
.authors("tasty").build();
when(gameMode.getDescription()).thenReturn(desc2); when(gameMode.getDescription()).thenReturn(desc2);
when(gameMode.getOverWorld()).thenReturn(world); when(gameMode.getOverWorld()).thenReturn(world);
@ -226,7 +219,9 @@ public class LevelTest {
when(plugin.getFlagsManager()).thenReturn(fm); when(plugin.getFlagsManager()).thenReturn(fm);
when(fm.getFlags()).thenReturn(Collections.emptyList()); when(fm.getFlags()).thenReturn(Collections.emptyList());
// Bukkit // Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(scheduler); when(Bukkit.getScheduler()).thenReturn(scheduler);
ItemMeta meta = mock(ItemMeta.class); ItemMeta meta = mock(ItemMeta.class);
ItemFactory itemFactory = mock(ItemFactory.class); ItemFactory itemFactory = mock(ItemFactory.class);
@ -265,7 +260,10 @@ public class LevelTest {
private static void deleteAll(File file) throws IOException { private static void deleteAll(File file) throws IOException {
if (file.exists()) { if (file.exists()) {
Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); Files.walk(file.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
} }
} }
@ -278,7 +276,7 @@ public class LevelTest {
verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world"); verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world");
verify(plugin).log("[Level] Level hooking into BSkyBlock"); verify(plugin).log("[Level] Level hooking into BSkyBlock");
verify(cmd, times(3)).getAddon(); // 3 commands verify(cmd, times(3)).getAddon(); // 3 commands
verify(adminCmd, times(5)).getAddon(); // Five commands verify(adminCmd, times(4)).getAddon(); // Four commands
// Placeholders // Placeholders
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_island_level"), any()); verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_island_level"), any());
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_visited_island_level"), any()); verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_visited_island_level"), any());
@ -302,4 +300,12 @@ public class LevelTest {
assertEquals(100, s.getLevelCost()); assertEquals(100, s.getLevelCost());
} }
/**
* Test method for {@link world.bentobox.level.Level#getRankLevel(World, int)}.
*/
@Test
public void testRankLevel() {
addon.onEnable();
assertEquals("",addon.getRankLevel(world, 1));
}
} }

View File

@ -4,7 +4,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -64,7 +63,6 @@ import world.bentobox.level.calculators.Pipeliner;
import world.bentobox.level.calculators.Results; import world.bentobox.level.calculators.Results;
import world.bentobox.level.config.ConfigSettings; import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.objects.TopTenData;
/** /**
* @author tastybento * @author tastybento
@ -83,6 +81,7 @@ public class LevelsManagerTest {
@Mock @Mock
private Settings pluginSettings; private Settings pluginSettings;
// Class under test // Class under test
private LevelsManager lm; private LevelsManager lm;
@Mock @Mock
@ -114,11 +113,12 @@ public class LevelsManagerTest {
@Mock @Mock
private BukkitScheduler scheduler; private BukkitScheduler scheduler;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@BeforeClass @BeforeClass
public static void beforeClass() { public static void beforeClass() {
// This has to be done beforeClass otherwise the tests will interfere with each // This has to be done beforeClass otherwise the tests will interfere with each other
// other
handler = mock(AbstractDatabaseHandler.class); handler = mock(AbstractDatabaseHandler.class);
// Database // Database
PowerMockito.mockStatic(DatabaseSetup.class); PowerMockito.mockStatic(DatabaseSetup.class);
@ -161,12 +161,12 @@ public class LevelsManagerTest {
when(island.getMemberSet()).thenReturn(iset); when(island.getMemberSet()).thenReturn(iset);
when(island.getOwner()).thenReturn(uuid); when(island.getOwner()).thenReturn(uuid);
when(island.getWorld()).thenReturn(world); when(island.getWorld()).thenReturn(world);
when(island.getUniqueId()).thenReturn(uuid.toString()); when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString());
// Default to uuid's being island owners // Default to uuid's being island owners
when(im.hasIsland(eq(world), any(UUID.class))).thenReturn(true); when(im.isOwner(eq(world), any())).thenReturn(true);
when(im.getIsland(world, uuid)).thenReturn(island); when(im.getOwner(any(), any(UUID.class))).thenAnswer(in -> in.getArgument(1, UUID.class));
when(im.getIsland(eq(world), eq(uuid))).thenReturn(island);
when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); when(im.getIslandById(anyString())).thenReturn(Optional.of(island));
when(im.getIslandById(anyString(), eq(false))).thenReturn(Optional.of(island));
// Player // Player
when(player.getUniqueId()).thenReturn(uuid); when(player.getUniqueId()).thenReturn(uuid);
@ -246,13 +246,15 @@ public class LevelsManagerTest {
private static void deleteAll(File file) throws IOException { private static void deleteAll(File file) throws IOException {
if (file.exists()) { if (file.exists()) {
Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); Files.walk(file.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
} }
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}.
* {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}.
*/ */
@Test @Test
public void testCalculateLevel() { public void testCalculateLevel() {
@ -267,21 +269,11 @@ public class LevelsManagerTest {
//Map<UUID, Long> tt = lm.getTopTen(world, 10); //Map<UUID, Long> tt = lm.getTopTen(world, 10);
//assertEquals(1, tt.size()); //assertEquals(1, tt.size());
//assertTrue(tt.get(uuid) == 10000); //assertTrue(tt.get(uuid) == 10000);
assertEquals(10000L, lm.getIslandMaxLevel(world, uuid));
results.setLevel(5000);
lm.calculateLevel(uuid, island);
// Complete the pipelined completable future
cf.complete(results);
assertEquals(5000L, lm.getLevelsData(island).getLevel());
// Still should be 10000
assertEquals(10000L, lm.getIslandMaxLevel(world, uuid));
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}.
* {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}.
*/ */
@Test @Test
public void testGetInitialLevel() { public void testGetInitialLevel() {
@ -289,8 +281,7 @@ public class LevelsManagerTest {
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}.
* {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}.
*/ */
@Test @Test
public void testGetIslandLevel() { public void testGetIslandLevel() {
@ -298,8 +289,7 @@ public class LevelsManagerTest {
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}.
* {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}.
*/ */
@Test @Test
public void testGetPointsToNextString() { public void testGetPointsToNextString() {
@ -310,8 +300,7 @@ public class LevelsManagerTest {
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}.
* {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}.
*/ */
@Test @Test
public void testGetIslandLevelString() { public void testGetIslandLevelString() {
@ -319,8 +308,7 @@ public class LevelsManagerTest {
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}.
* {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}.
*/ */
@Test @Test
public void testGetLevelsData() { public void testGetLevelsData() {
@ -344,44 +332,39 @@ public class LevelsManagerTest {
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
* {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
*/ */
@Test @Test
public void testGetTopTenEmpty() { public void testGetTopTenEmpty() {
Map<String, Long> tt = lm.getTopTen(world, Level.TEN); Map<UUID, Long> tt = lm.getTopTen(world, 10);
assertTrue(tt.isEmpty()); assertTrue(tt.isEmpty());
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
* {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
*/ */
@Test @Test
public void testGetTopTen() { public void testGetTopTen() {
testLoadTopTens(); testLoadTopTens();
Map<String, Long> tt = lm.getTopTen(world, Level.TEN); Map<UUID, Long> tt = lm.getTopTen(world, 10);
assertFalse(tt.isEmpty()); assertFalse(tt.isEmpty());
assertEquals(1, tt.size()); assertEquals(1, tt.size());
assertEquals(1, lm.getTopTen(world, 1).size()); assertEquals(1, lm.getTopTen(world, 1).size());
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
* {@link world.bentobox.level.LevelsManager#getWeightedTopTen(org.bukkit.World, int)}.
*/ */
@Test @Test
public void testGetWeightedTopTen() { public void testGetTopTenNoOwners() {
when(im.isOwner(eq(world), any())).thenReturn(false);
testLoadTopTens(); testLoadTopTens();
Map<Island, Long> tt = lm.getWeightedTopTen(world, Level.TEN); Map<UUID, Long> tt = lm.getTopTen(world, 10);
assertFalse(tt.isEmpty()); assertTrue(tt.isEmpty());
assertEquals(1, tt.size());
assertEquals(1, lm.getTopTen(world, 1).size());
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}.
* {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}.
*/ */
@Test @Test
public void testHasTopTenPerm() { public void testHasTopTenPerm() {
@ -397,40 +380,45 @@ public class LevelsManagerTest {
lm.loadTopTens(); lm.loadTopTens();
PowerMockito.verifyStatic(Bukkit.class); // 1 PowerMockito.verifyStatic(Bukkit.class); // 1
Bukkit.getScheduler(); Bukkit.getScheduler();
verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); // Capture the task in the scheduler verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture());
task.getValue().run(); // run it task.getValue().run();
verify(addon).log("Generating rankings"); verify(addon).log(eq("Generating Top Ten Tables"));
verify(addon).log("Generated rankings for bskyblock-world"); verify(addon).log(eq("Loaded top ten for bskyblock-world"));
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}.
* {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}.
*/ */
@Test @Test
public void testRemoveEntry() { public void testRemoveEntry() {
testLoadTopTens(); testLoadTopTens();
Map<String, Long> tt = lm.getTopTen(world, Level.TEN); Map<UUID, Long> tt = lm.getTopTen(world, 10);
assertTrue(tt.containsKey(uuid.toString())); assertTrue(tt.containsKey(uuid));
lm.removeEntry(world, uuid.toString()); lm.removeEntry(world, uuid);
tt = lm.getTopTen(world, Level.TEN); tt = lm.getTopTen(world, 10);
assertFalse(tt.containsKey(uuid.toString())); assertFalse(tt.containsKey(uuid));
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#save()}.
* {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}. */
@Test
public void testSave() {
lm.save();
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}.
*/ */
@Test @Test
public void testSetInitialIslandLevel() { public void testSetInitialIslandLevel() {
lm.setInitialIslandLevel(island, Level.TEN); lm.setInitialIslandLevel(island, 10);
assertEquals(Level.TEN, lm.getInitialLevel(island)); assertEquals(10, lm.getInitialLevel(island));
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}.
* {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}.
*/ */
@Test @Test
public void testSetIslandLevel() { public void testSetIslandLevel() {
@ -440,25 +428,19 @@ public class LevelsManagerTest {
} }
/** /**
* Test method for * Test method for {@link world.bentobox.level.LevelsManager#getGUI(org.bukkit.World, world.bentobox.bentobox.api.user.User)}.
* {@link world.bentobox.level.LevelsManager#getRank(World, UUID)}
*/ */
@Test @Test
public void testGetRank() { public void testGetGUI() {
lm.createAndCleanRankings(world); lm.getGUI(world, user);
Map<World, TopTenData> ttl = lm.getTopTenLists(); verify(user).getTranslation(eq("island.top.gui-title"));
Map<String, Long> tt = ttl.get(world).getTopTen(); verify(player).openInventory(inv);
for (long i = 100; i < 150; i++) { /*
tt.put(UUID.randomUUID().toString(), i); int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
for (int i : SLOTS) {
verify(inv).setItem(eq(i), any());
} }
// Put island as lowest rank */
tt.put(uuid.toString(), 10L);
assertEquals(51, lm.getRank(world, uuid));
// Put island as highest rank
tt.put(uuid.toString(), 1000L);
assertEquals(1, lm.getRank(world, uuid));
// Unknown UUID - lowest rank + 1
assertEquals(52, lm.getRank(world, UUID.randomUUID()));
} }
} }

View File

@ -1,333 +0,0 @@
package world.bentobox.level;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Location;
import org.bukkit.World;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.AddonDescription;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlaceholdersManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.level.objects.IslandLevels;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ BentoBox.class })
public class PlaceholderManagerTest {
@Mock
private Level addon;
@Mock
private GameModeAddon gm;
@Mock
private BentoBox plugin;
private PlaceholderManager phm;
@Mock
private PlaceholdersManager bpm;
@Mock
private LevelsManager lm;
@Mock
private World world;
@Mock
private IslandsManager im;
@Mock
private Island island;
@Mock
private User user;
private static final Map<UUID, String> names = new LinkedHashMap<>();
static {
names.put(UUID.randomUUID(), "tasty");
names.put(UUID.randomUUID(), "bento");
names.put(UUID.randomUUID(), "fred");
names.put(UUID.randomUUID(), "bonne");
names.put(UUID.randomUUID(), "cyprien");
names.put(UUID.randomUUID(), "mael");
names.put(UUID.randomUUID(), "joe");
names.put(UUID.randomUUID(), "horacio");
names.put(UUID.randomUUID(), "steph");
names.put(UUID.randomUUID(), "vicky");
}
private Map<String, Island> islands = new HashMap<>();
private Map<String, Long> map = new LinkedHashMap<>();
private Map<Island, Long> map2 = new LinkedHashMap<>();
private @NonNull IslandLevels data;
@Mock
private PlayersManager pm;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
when(addon.getPlugin()).thenReturn(plugin);
// Users
when(addon.getPlayers()).thenReturn(pm);
// Users
when(user.getWorld()).thenReturn(world);
when(user.getLocation()).thenReturn(mock(Location.class));
int i = 0;
for (Entry<UUID, String> n : names.entrySet()) {
UUID uuid = UUID.randomUUID(); // Random island ID
Long value = (long)(100 - i++);
map.put(uuid.toString(), value); // level
Island is = new Island();
is.setUniqueId(uuid.toString());
is.setOwner(n.getKey());
is.setName(n.getValue() + "'s island");
islands.put(uuid.toString(), is);
map2.put(is, value);
}
// Sort
map = map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
when(pm.getName(any())).thenAnswer((Answer<String>) invocation -> names.getOrDefault(invocation.getArgument(0, UUID.class), "unknown"));
Map<UUID, Integer> members = new HashMap<>();
names.forEach((uuid, l) -> members.put(uuid, RanksManager.MEMBER_RANK));
islands.values().forEach(is -> is.setMembers(members));
// Placeholders manager for plugin
when(plugin.getPlaceholdersManager()).thenReturn(bpm);
// Game mode
AddonDescription desc = new AddonDescription.Builder("bentobox", "AOneBlock", "1.3").description("test").authors("tasty").build();
when(gm.getDescription()).thenReturn(desc);
when(gm.getOverWorld()).thenReturn(world);
when(gm.inWorld(world)).thenReturn(true);
// Islands
when(im.getIsland(any(World.class), any(User.class))).thenReturn(island);
when(im.getIslandAt(any(Location.class))).thenReturn(Optional.of(island));
when(im.getIslandById(anyString())).thenAnswer((Answer<Optional<Island>>) invocation -> Optional.of(islands.get(invocation.getArgument(0, String.class))));
when(im.getIslands(any(), any(UUID.class))).thenReturn(new ArrayList<>(islands.values()));
when(addon.getIslands()).thenReturn(im);
// Levels Manager
when(lm.getIslandLevel(any(), any())).thenReturn(1234567L);
when(lm.getIslandLevelString(any(), any())).thenReturn("1234567");
when(lm.getPointsToNextString(any(), any())).thenReturn("1234567");
when(lm.getIslandMaxLevel(any(), any())).thenReturn(987654L);
when(lm.getTopTen(world, Level.TEN)).thenReturn(map);
when(lm.getWeightedTopTen(world, Level.TEN)).thenReturn(map2);
when(lm.formatLevel(any())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, Long.class).toString());
data = new IslandLevels("uniqueId");
data.setTotalPoints(12345678);
when(lm.getLevelsData(island)).thenReturn(data);
when(addon.getManager()).thenReturn(lm);
phm = new PlaceholderManager(addon);
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#PlaceholderManager(world.bentobox.level.Level)}.
*/
@Test
public void testPlaceholderManager() {
verify(addon).getPlugin();
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#registerPlaceholders(world.bentobox.bentobox.api.addons.GameModeAddon)}.
*/
@Test
public void testRegisterPlaceholders() {
phm.registerPlaceholders(gm);
// Island Level
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level"), any());
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_raw"), any());
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_total_points"), any());
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_points_to_next_level"), any());
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_max"), any());
// Visited Island Level
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_visited_island_level"), any());
// Register Top Ten Placeholders
for (int i = 1; i < 11; i++) {
// Name
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_name_" + i), any());
// Island Name
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_island_name_" + i), any());
// Members
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_members_" + i), any());
// Level
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_value_" + i), any());
}
// Personal rank
verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_rank_value"), any());
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#getRankName(org.bukkit.World, int)}.
*/
@Test
public void testGetRankName() {
// Test extremes
assertEquals("tasty", phm.getRankName(world, 0, false));
assertEquals("vicky", phm.getRankName(world, 100, false));
// Test the ranks
int rank = 1;
for (String name : names.values()) {
assertEquals(name, phm.getRankName(world, rank++, false));
}
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#getRankIslandName(org.bukkit.World, int)}.
*/
@Test
public void testGetRankIslandName() {
// Test extremes
assertEquals("tasty's island", phm.getRankIslandName(world, 0, false));
assertEquals("vicky's island", phm.getRankIslandName(world, 100, false));
// Test the ranks
int rank = 1;
for (String name : names.values()) {
assertEquals(name + "'s island", phm.getRankIslandName(world, rank++, false));
}
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#getRankMembers(org.bukkit.World, int)}.
*/
@Test
public void testGetRankMembers() {
// Test extremes
check(1, phm.getRankMembers(world, 0, false));
check(2, phm.getRankMembers(world, 100, false));
// Test the ranks
for (int rank = 1; rank < 11; rank++) {
check(3, phm.getRankMembers(world, rank, false));
}
}
void check(int indicator, String list) {
for (String n : names.values()) {
assertTrue(n + " is missing for test " + indicator, list.contains(n));
}
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#getRankLevel(org.bukkit.World, int)}.
*/
@Test
public void testGetRankLevel() {
// Test extremes
assertEquals("100", phm.getRankLevel(world, 0, false));
assertEquals("91", phm.getRankLevel(world, 100, false));
// Test the ranks
for (int rank = 1; rank < 11; rank++) {
assertEquals(String.valueOf(101 - rank), phm.getRankLevel(world, rank, false));
}
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#getRankLevel(org.bukkit.World, int)}.
*/
@Test
public void testGetWeightedRankLevel() {
// Test extremes
assertEquals("100", phm.getRankLevel(world, 0, true));
assertEquals("91", phm.getRankLevel(world, 100, true));
// Test the ranks
for (int rank = 1; rank < 11; rank++) {
assertEquals(String.valueOf(101 - rank), phm.getRankLevel(world, rank, true));
}
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}.
*/
@Test
public void testGetVisitedIslandLevelNullUser() {
assertEquals("", phm.getVisitedIslandLevel(gm, null));
}
/**
* Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}.
*/
@Test
public void testGetVisitedIslandLevelUserNotInWorld() {
// Another world
when(user.getWorld()).thenReturn(mock(World.class));
assertEquals("", phm.getVisitedIslandLevel(gm, user));
}
/**
* Test method for
* {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}.
*/
@Test
public void testGetVisitedIslandLevel() {
assertEquals("1234567", phm.getVisitedIslandLevel(gm, user));
}
/**
* Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}.
*/
@Test
public void testGetVisitedIslandLevelNoIsland() {
when(im.getIslandAt(any(Location.class))).thenReturn(Optional.empty());
assertEquals("0", phm.getVisitedIslandLevel(gm, user));
}
}

View File

@ -1,37 +0,0 @@
package world.bentobox.level.calculators;
import static org.junit.Assert.assertEquals;
import java.text.ParseException;
import org.junit.Test;
/**
* Test the equation evaluation
*/
public class EquationEvaluatorTest {
/**
* Test method for {@link world.bentobox.level.calculators.EquationEvaluator#eval(java.lang.String)}.
* @throws ParseException
*/
@Test
public void testEval() throws ParseException {
assertEquals(4D, EquationEvaluator.eval("2+2"), 0D);
assertEquals(0D, EquationEvaluator.eval("2-2"), 0D);
assertEquals(1D, EquationEvaluator.eval("2/2"), 0D);
assertEquals(4D, EquationEvaluator.eval("2*2"), 0D);
assertEquals(8D, EquationEvaluator.eval("2+2+2+2"), 0D);
assertEquals(5D, EquationEvaluator.eval("2.5+2.5"), 0D);
assertEquals(1.414, EquationEvaluator.eval("sqrt(2)"), 0.001D);
assertEquals(3.414, EquationEvaluator.eval("2 + sqrt(2)"), 0.001D);
assertEquals(0D, EquationEvaluator.eval("sin(0)"), 0.1D);
assertEquals(1D, EquationEvaluator.eval("cos(0)"), 0.1D);
assertEquals(0D, EquationEvaluator.eval("tan(0)"), 0.1D);
assertEquals(0D, EquationEvaluator.eval("log(1)"), 0.1D);
assertEquals(27D, EquationEvaluator.eval("3^3"), 0.D);
assertEquals(84.70332D, EquationEvaluator.eval("3^3 + 2 + 2.65 * (3 / 4) - sin(45) * log(10) + 55.344"),
0.0001D);
}
}

View File

@ -1,183 +0,0 @@
package world.bentobox.level.commands;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.meta.ItemMeta;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.level.Level;
import world.bentobox.level.LevelsManager;
import world.bentobox.level.objects.TopTenData;
/**
* @author tastybento
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Bukkit.class, BentoBox.class })
public class AdminStatsCommandTest {
@Mock
private CompositeCommand ic;
private UUID uuid;
@Mock
private User user;
@Mock
private IslandsManager im;
@Mock
private Island island;
@Mock
private Level addon;
@Mock
private World world;
@Mock
private IslandWorldManager iwm;
@Mock
private GameModeAddon gameModeAddon;
@Mock
private Player p;
@Mock
private LocalesManager lm;
@Mock
private PlayersManager pm;
private AdminStatsCommand asc;
private TopTenData ttd;
@Mock
private LevelsManager manager;
@Mock
private Server server;
@Before
public void setUp() {
// Set up plugin
BentoBox plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
User.setPlugin(plugin);
when(addon.getPlugin()).thenReturn(plugin);
// Addon
when(ic.getAddon()).thenReturn(addon);
when(ic.getPermissionPrefix()).thenReturn("bskyblock.");
when(ic.getLabel()).thenReturn("island");
when(ic.getTopLabel()).thenReturn("island");
when(ic.getWorld()).thenReturn(world);
when(ic.getTopLabel()).thenReturn("bsb");
// IWM friendly name
when(plugin.getIWM()).thenReturn(iwm);
when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
// World
when(world.toString()).thenReturn("world");
when(world.getName()).thenReturn("BSkyBlock_world");
// Player manager
when(plugin.getPlayers()).thenReturn(pm);
when(pm.getUser(anyString())).thenReturn(user);
// topTen
when(addon.getManager()).thenReturn(manager);
// User
uuid = UUID.randomUUID();
when(user.getUniqueId()).thenReturn(uuid);
when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class));
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getServer()).thenReturn(server);
// Mock item factory (for itemstacks)
ItemFactory itemFactory = mock(ItemFactory.class);
ItemMeta itemMeta = mock(ItemMeta.class);
when(itemFactory.getItemMeta(any())).thenReturn(itemMeta);
when(server.getItemFactory()).thenReturn(itemFactory);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
// Top ten
ttd = new TopTenData(world);
Map<String, Long> topten = new HashMap<>();
Random r = new Random();
for (int i = 0; i < 1000; i++) {
topten.put(UUID.randomUUID().toString(), r.nextLong(20000));
}
ttd.setTopTen(topten);
asc = new AdminStatsCommand(addon, ic);
}
@After
public void tearDown() {
User.clearUsers();
}
/**
* Test method for
* {@link world.bentobox.level.commands.AdminStatsCommand#setup()}.
*/
@Test
public void testSetup() {
assertEquals("bskyblock.admin.stats", asc.getPermission());
assertFalse(asc.isOnlyPlayer());
assertEquals("admin.stats.description", asc.getDescription());
}
/**
* Test method for
* {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testExecuteUserStringListOfString() {
assertFalse(asc.execute(user, "", List.of()));
verify(user).sendMessage("admin.stats.title");
verify(user).sendMessage("admin.stats.no-data");
}
/**
* Test method for
* {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testExecuteUserStringListOfStringLevels() {
Map<World, TopTenData> map = new HashMap<>();
map.put(world, ttd);
when(manager.getTopTenLists()).thenReturn(map);
assertTrue(asc.execute(user, "", List.of()));
verify(user).sendMessage("admin.stats.title");
verify(user, never()).sendMessage("admin.stats.no-data");
}
}

View File

@ -1,208 +0,0 @@
package world.bentobox.level.commands;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.meta.ItemMeta;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.level.Level;
import world.bentobox.level.LevelsManager;
import world.bentobox.level.objects.TopTenData;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Bukkit.class, BentoBox.class })
public class AdminTopRemoveCommandTest {
@Mock
private CompositeCommand ic;
private UUID uuid;
@Mock
private User user;
@Mock
private IslandsManager im;
@Mock
private Island island;
@Mock
private Level addon;
@Mock
private World world;
@Mock
private IslandWorldManager iwm;
@Mock
private GameModeAddon gameModeAddon;
@Mock
private Player p;
@Mock
private LocalesManager lm;
@Mock
private PlayersManager pm;
private AdminTopRemoveCommand atrc;
@Mock
private TopTenData ttd;
@Mock
private LevelsManager manager;
@Mock
private Server server;
@Before
public void setUp() {
// Set up plugin
BentoBox plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
User.setPlugin(plugin);
// Addon
when(ic.getAddon()).thenReturn(addon);
when(ic.getPermissionPrefix()).thenReturn("bskyblock.");
when(ic.getLabel()).thenReturn("island");
when(ic.getTopLabel()).thenReturn("island");
when(ic.getWorld()).thenReturn(world);
when(ic.getTopLabel()).thenReturn("bsb");
// IWM friendly name
when(plugin.getIWM()).thenReturn(iwm);
when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
// World
when(world.toString()).thenReturn("world");
when(world.getName()).thenReturn("BSkyBlock_world");
// Player manager
when(plugin.getPlayers()).thenReturn(pm);
when(pm.getUser(anyString())).thenReturn(user);
// topTen
when(addon.getManager()).thenReturn(manager);
// User
uuid = UUID.randomUUID();
when(user.getUniqueId()).thenReturn(uuid);
when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class));
// Island
when(island.getUniqueId()).thenReturn(uuid.toString());
when(island.getOwner()).thenReturn(uuid);
// Island Manager
when(plugin.getIslands()).thenReturn(im);
when(im.getIslands(any(), any(User.class))).thenReturn(List.of(island));
when(im.getIslands(any(), any(UUID.class))).thenReturn(List.of(island));
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getServer()).thenReturn(server);
// Mock item factory (for itemstacks)
ItemFactory itemFactory = mock(ItemFactory.class);
ItemMeta itemMeta = mock(ItemMeta.class);
when(itemFactory.getItemMeta(any())).thenReturn(itemMeta);
when(server.getItemFactory()).thenReturn(itemFactory);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
atrc = new AdminTopRemoveCommand(addon, ic);
}
@After
public void tearDown() {
User.clearUsers();
}
/**
* Test method for
* {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#AdminTopRemoveCommand(world.bentobox.level.Level, world.bentobox.bentobox.api.commands.CompositeCommand)}.
*/
@Test
public void testAdminTopRemoveCommand() {
assertEquals("remove", atrc.getLabel());
assertEquals("delete", atrc.getAliases().get(0));
}
/**
* Test method for
* {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#setup()}.
*/
@Test
public void testSetup() {
assertEquals("bskyblock.admin.top.remove", atrc.getPermission());
assertEquals("admin.top.remove.parameters", atrc.getParameters());
assertEquals("admin.top.remove.description", atrc.getDescription());
assertFalse(atrc.isOnlyPlayer());
}
/**
* Test method for
* {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteWrongArgs() {
assertFalse(atrc.canExecute(user, "delete", Collections.emptyList()));
verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "BSkyBlock");
}
/**
* Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteUnknown() {
when(pm.getUser(anyString())).thenReturn(null);
assertFalse(atrc.canExecute(user, "delete", Collections.singletonList("tastybento")));
verify(user).sendMessage("general.errors.unknown-player", TextVariables.NAME, "tastybento");
}
/**
* Test method for
* {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteKnown() {
assertTrue(atrc.canExecute(user, "delete", Collections.singletonList("tastybento")));
}
/**
* Test method for
* {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testExecuteUserStringListOfString() {
testCanExecuteKnown();
assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento")));
verify(manager).removeEntry(world, uuid.toString());
verify(user).sendMessage("general.success");
}
}

View File

@ -0,0 +1,199 @@
package world.bentobox.level.commands.admin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.meta.ItemMeta;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.level.Level;
import world.bentobox.level.LevelsManager;
import world.bentobox.level.commands.AdminTopRemoveCommand;
import world.bentobox.level.objects.TopTenData;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, BentoBox.class})
public class AdminTopRemoveCommandTest {
@Mock
private CompositeCommand ic;
private UUID uuid;
@Mock
private User user;
@Mock
private IslandsManager im;
@Mock
private Island island;
@Mock
private Level addon;
@Mock
private World world;
@Mock
private IslandWorldManager iwm;
@Mock
private GameModeAddon gameModeAddon;
@Mock
private Player p;
@Mock
private LocalesManager lm;
@Mock
private PlayersManager pm;
private AdminTopRemoveCommand atrc;
@Mock
private TopTenData ttd;
@Mock
private LevelsManager manager;
@Mock
private Server server;
@Before
public void setUp() {
// Set up plugin
BentoBox plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
User.setPlugin(plugin);
// Addon
when(ic.getAddon()).thenReturn(addon);
when(ic.getPermissionPrefix()).thenReturn("bskyblock.");
when(ic.getLabel()).thenReturn("island");
when(ic.getTopLabel()).thenReturn("island");
when(ic.getWorld()).thenReturn(world);
when(ic.getTopLabel()).thenReturn("bsb");
// IWM friendly name
when(plugin.getIWM()).thenReturn(iwm);
when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
// World
when(world.toString()).thenReturn("world");
when(world.getName()).thenReturn("BSkyBlock_world");
// Player manager
when(plugin.getPlayers()).thenReturn(pm);
when(pm.getUser(anyString())).thenReturn(user);
// topTen
when(addon.getManager()).thenReturn(manager);
// User
uuid = UUID.randomUUID();
when(user.getUniqueId()).thenReturn(uuid);
when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class));
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getServer()).thenReturn(server);
// Mock item factory (for itemstacks)
ItemFactory itemFactory = mock(ItemFactory.class);
ItemMeta itemMeta = mock(ItemMeta.class);
when(itemFactory.getItemMeta(any())).thenReturn(itemMeta);
when(server.getItemFactory()).thenReturn(itemFactory);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
atrc = new AdminTopRemoveCommand(addon, ic);
}
@After
public void tearDown() {
User.clearUsers();
}
/**
* Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#AdminTopRemoveCommand(world.bentobox.level.Level, world.bentobox.bentobox.api.commands.CompositeCommand)}.
*/
@Test
public void testAdminTopRemoveCommand() {
assertEquals("remove", atrc.getLabel());
assertEquals("delete", atrc.getAliases().get(0));
}
/**
* Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#setup()}.
*/
@Test
public void testSetup() {
assertEquals("bskyblock.admin.top.remove", atrc.getPermission());
assertEquals("admin.top.remove.parameters", atrc.getParameters());
assertEquals("admin.top.remove.description", atrc.getDescription());
assertFalse(atrc.isOnlyPlayer());
}
/**
* Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteWrongArgs() {
assertFalse(atrc.canExecute(user, "delete", Collections.emptyList()));
verify(user).sendMessage(eq("commands.help.header"), eq(TextVariables.LABEL), eq("BSkyBlock"));
}
/**
* Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteUnknown() {
when(pm.getUser(anyString())).thenReturn(null);
assertFalse(atrc.canExecute(user, "delete", Collections.singletonList("tastybento")));
verify(user).sendMessage(eq("general.errors.unknown-player"), eq(TextVariables.NAME), eq("tastybento"));
}
/**
* Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteKnown() {
assertTrue(atrc.canExecute(user, "delete", Collections.singletonList("tastybento")));
}
/**
* Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testExecuteUserStringListOfString() {
testCanExecuteKnown();
assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento")));
verify(manager).removeEntry(any(World.class), eq(uuid));
verify(user).sendMessage(eq("general.success"));
}
}