Compare commits

...

108 Commits

Author SHA1 Message Date
tastybento 5dee0d2426 Fix space in locale file 2024-05-05 13:37:24 -07:00
tastybento 1369ffb8c8 Remove unused imports. 2024-05-05 13:34:25 -07:00
tastybento e2cbd18f17
Merge branch 'master' into develop 2024-05-05 13:32:47 -07:00
tastybento 4d16e9c227
Implement a cache for top tens (#309) 2024-05-04 12:36:03 -07:00
tastybento a4f8d12138
Isolate UltimateStacker imports so no errors if US is not installed (#307)
Fixes #306
2024-04-20 09:50:17 -07:00
Huynh Tien 1bd6219f94
Update hooks and fix UltimateStacker API (#305) 2024-04-19 07:31:23 -07:00
add5tar 2d1f618676
Add more string replacements for /is level output (#303)
* Add more string replacements to output

* Forgot to include the locale change
2024-04-16 17:55:17 -07:00
tastybento 774bbd034c Version 2.13.0 2024-02-03 08:28:44 -08:00
tastybento 02a19d1bdb Update tipped arrows in GUI Panel 2024-01-21 09:08:35 -08:00
tastybento 952a2a6e81 Version 2.12.1 2024-01-21 09:08:30 -08:00
tastybento 79f988f460 Fix merge error 2024-01-13 19:48:14 -08:00
tastybento ae030eb548
Merge branch 'master' into develop 2024-01-13 19:45:40 -08:00
tastybento 76e0bad88a Added test class for EquationEvaluator 2024-01-13 08:50:37 -08:00
tastybento 2b373f62d9
Uses latest Visit API to avoid chat spam. Fixes #299 (#300) 2024-01-02 14:19:16 +09:00
tastybento 5f83a81f18 Add protection around unknown blockconfig.yml entries. GRASS>SHORT_GRASS 2024-01-02 11:24:07 +09:00
tastybento 82174649b4 Added comments on the panel templates. 2023-12-18 17:57:36 -08:00
tastybento cec620162b Move to 1.20.4
Refactored the calculator code for clarity.

Added Jacoco line to prvent issues with the bigger Material class.
2023-12-10 10:06:33 -08:00
gitlocalize-app[bot] cfb35909f0
Translate uk.yml via GitLocalize (#297)
Co-authored-by: GIGABAIT <freebitcoin93@gmail.com>
2023-11-26 10:06:35 -08:00
tastybento 117d15f3d0 Added more placeholders. #296
Refactored how the top ten maps are structured. In the future, it may be
best to have the key be the island.
2023-11-24 09:33:16 -08:00
tastybento 9d1a5c7476
Changed top ten internally to use islands instead of players as keys (#295)
Added %[gamemode]_top_weighted_value_x% placeholder
https://github.com/BentoBoxWorld/Level/issues/294
2023-11-19 18:08:47 -08:00
tastybento 26d4839f6a Try lower version of jacoco 2023-11-18 15:26:33 -08:00
tastybento 43c898ecf7 Added test for Stats command 2023-11-18 15:22:05 -08:00
tastybento 77884f0a11 Update to BentoBox 2.0.0 API 2023-11-18 14:51:09 -08:00
tastybento 1a4077be8c Update tests 2023-11-18 14:44:49 -08:00
tastybento 3a3c8a320c Adds an admin stats command. See #293 2023-11-18 14:41:18 -08:00
tastybento eb71b35c5c Version 2.12.0 2023-11-18 13:06:44 -08:00
tastybento cdd4366a2a Merge branch 'develop' of https://github.com/BentoBoxWorld/Level.git
into develop
2023-11-12 09:43:42 -08:00
tastybento 8f567cc328 Use 2.0.0 BentoBox API 2023-11-12 09:42:43 -08:00
PapiCapi 913eed9c77
Add config option to disable plugin hooks (#291)
* Update UltimateStacker dependency

* Add config option to disable plugin hooks
2023-10-06 16:14:45 -07:00
tastybento 2b0a6d82ef
Update pom.xml 2023-07-10 21:44:22 -07:00
tastybento a3537f5b80 Update Jacoco 2023-07-10 21:23:33 -07:00
tastybento 1b02f11220 Added distribution required for Github Action 2023-06-24 13:56:20 -07:00
tastybento 0c42ad866a Merge branch 'develop' of https://github.com/BentoBoxWorld/Level.git into develop 2023-06-24 13:45:15 -07:00
tastybento deb09992f1 Update Github Build script 2023-06-24 13:45:07 -07:00
tastybento 1b20605791 Version 2.11.0 2023-06-03 09:15:32 -07:00
gitlocalize-app[bot] 951b0f5549
Spanish (#285)
* Translate es.yml via GitLocalize

* Translate es.yml via GitLocalize

---------

Co-authored-by: ChrissTM03 <criisbr193@gmail.com>
Co-authored-by: Espan <zcraftyt@gmail.com>
2023-05-29 09:47:02 -07:00
gitlocalize-app[bot] 30217ba509
French (#286)
* Translate fr.yml via GitLocalize

* Translate fr.yml via GitLocalize

* Translate fr.yml via GitLocalize

---------

Co-authored-by: gitlocalize-app[bot] <55277160+gitlocalize-app[bot]@users.noreply.github.com>
Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>
Co-authored-by: Florian CUNY <poslovitch@bentobox.world>
2023-05-29 09:46:48 -07:00
gitlocalize-app[bot] f8d50a43d6
Translate id.yml via GitLocalize (#287)
Co-authored-by: Dusty <siapa-yg-mau-diblokir.kfrxp@simplelogin.com>
2023-05-29 09:46:35 -07:00
gitlocalize-app[bot] dfd30d148b
Chinese (#288)
* Translate zh-CN.yml via GitLocalize

* Translate zh-CN.yml via GitLocalize

---------

Co-authored-by: Jeansou <bettertreebot@gmail.com>
Co-authored-by: dawnTak <lanlongxiaode@outlook.com>
2023-05-29 09:46:22 -07:00
tastybento 5a66bf162b Add blocks that should be zero by default as they are available
on the ocean floor. https://github.com/BentoBoxWorld/Level/issues/284
2023-05-17 15:30:38 -07:00
tastybento ad3d02c29a Version 2.10.1 2023-04-15 16:45:58 -07:00
tastybento 34b2b52979 Merge remote-tracking branch 'origin/master' into develop 2023-04-15 16:45:07 -07:00
tastybento 3c79c68e80 Updated dependencies 2023-04-15 11:38:19 -07:00
BONNe 78c67b6d2f
Create plugin.yml (#282)
* Create plugin.yml

The annotations do not provide the option to define the version dynamically from maven. This should fix that.

* Remove Spigot Plugin Annotations

* Remove plugin-annotation repo
2023-04-08 08:22:41 -07:00
ceze88 9c42a8d007
Add UltimateStacker hook for stacked blocks (#281) 2023-04-05 12:14:16 +01:00
tastybento c093796a6e Remove dependency 2023-03-25 09:53:41 -07:00
tastybento 713a409584
Refactor placeholders (#279)
* Update ReadMe

* Fix Jacoco

* Remove unused imports

* Remove placeholders from main class

Created a separate class for cleaner code and added a test class.
2023-02-25 11:31:49 -08:00
tastybento 0cdb15403b
Sonar Cloud code smell clean up (#278) 2023-02-09 19:32:13 -08:00
DevSolaris a493c12f6e
Add shulker to in chest count (#275) 2023-02-09 19:31:35 -08:00
tastybento 42249a8fc9 Updated Jacoco POM 2023-02-09 17:05:27 -08:00
tastybento f469e37702
Add ${argLine} to get jacoco coverage 2023-02-09 15:15:15 -08:00
gitlocalize-app[bot] 29b148052a
Translate nl.yml via GitLocalize (#277)
Co-authored-by: DevSolaris <solaris.dev.2002@gmail.com>
2023-01-24 14:46:02 +02:00
gitlocalize-app[bot] f1db2a9284
Translate zh-CN.yml via GitLocalize (#276)
Co-authored-by: dawnTak <lanlongxiaode@outlook.com>
2023-01-23 20:44:30 +02:00
tastybento dc9d460e1e Fix JavaDoc 2023-01-16 15:07:00 -08:00
evlad fba73948c6
feat: add island total points + placeholder (#264)
* feat: add island total points + placeholder

* Update IslandLevels.java
2023-01-16 23:00:40 +00:00
tastybento ac6bead52e Add natural log to level-calc formula parsing
Relates to #274
2023-01-16 14:16:14 -08:00
tastybento 93869cb34a Only shows Members or higher in the top members placeholder
Fixes #267
2022-11-26 18:29:03 -08:00
tastybento f3ee8a381c Adds %Level_[gamemode]_island_level_max% placeholder
This records the lifetime maximum level the island has ever had.
Addresses #271
2022-11-26 18:20:14 -08:00
tastybento 3988659dcc Update Github workflow to Java 17 2022-11-26 17:52:31 -08:00
tastybento 51338d280d Update to Java 17 2022-11-26 17:48:55 -08:00
gitlocalize-app[bot] 97d9522563
Translate fr.yml via GitLocalize (#272)
Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>
2022-10-31 08:22:33 +02:00
gitlocalize-app[bot] 32690630d6
Translate pl.yml via GitLocalize (#269)
Co-authored-by: wiktorm12 <wiktorm12@gmail.com>
2022-08-27 15:14:55 +03:00
BONNe 2ca4e0a070 Fixes a Level addon crash on startup.
Level addon crashed at the startup if Visit or Warps addon were not installed. It happened because Level addon main class were implementing Listener interface.
To avoid it and fix the crash, I moved migration listener to a separate class.

Fixes #2012
2022-08-21 17:31:42 +03:00
BONNe dae3db6c98 Implements visit/warp actions in top gui
Add 2 new actions for island buttons in TOP GUI:
- Visit -> allows to visit island, but it requires Visit Addon
- Warp -> allows to warp to island, but it requires Warp Addon

Requested via Discord.
2022-08-21 13:00:56 +03:00
DeadSilenceIV 90ae98e599
Support for AdvancedChests was updated. (#266) 2022-07-15 01:53:27 +03:00
BONNe 47053fde31
Implement customizable Values GUI. (#262)
This GUI shows value to all items in game. It also shows max limit of blocks, if it is set.

Fixes of #192
2022-06-17 14:40:10 +03:00
KrazyxWolf cc90579f51
Update es.yml (#261) 2022-06-16 21:21:40 +03:00
BONNe 1914fc11e0
Implement calculated value for blocks. (#260)
It is ~ value, as calculation formula cannot be applied per block. At least I think so.

Part of #192
2022-06-16 17:38:09 +03:00
BONNe 4948689fe8
Implement feature that allows to sort items in detail panel. (#259)
Apparently, because it is 2 years old request, it got in a state -> implement or drop.

Fixes #192
2022-06-16 17:07:09 +03:00
tastybento fcf6e76599 Added repo for maven plugin snapshots 2022-03-19 18:33:36 +00:00
tastybento d9288c7e61 Remove blank file 2022-03-19 18:27:58 +00:00
BONNe eb8c105be5 Fix failing test. 2022-03-13 15:24:12 +02:00
BONNe 15ff515078 Implement customizable DetailsPanel.
Remove old DetailsGUITab due to new implementation.
2022-03-13 14:28:08 +02:00
BONNe 43fcde5781 Fixes some small issues with TopLevelPanel
Add Utils class that contains some useful things.
2022-03-13 14:26:56 +02:00
BONNe e16fad882e Update to BentoBox API 1.20.
Replace plugin.yml with spigot-annotations.

Implement customizable TopLevelPanel.
2022-03-12 12:52:44 +02:00
tastybento 0a79b7fa58 Avoid async chunk snapshotting.
Fixes https://github.com/BentoBoxWorld/Level/issues/256
2022-02-06 08:47:20 -08:00
tastybento 5d9aa00c13 Fix error lon loading id locale 2022-02-06 08:40:56 -08:00
tastybento 490fe6c942 Merge branch 'develop' of https://github.com/BentoBoxWorld/Level.git into develop 2022-01-28 22:15:52 -08:00
tastybento 336e8d47bf Attempt to handle WildStacker spawners 2022-01-28 22:15:34 -08:00
tastybento a3d06ee41a Version 2.9.1 2022-01-28 22:15:19 -08:00
gitlocalize-app[bot] 6f174e2b3a
Translate hu.yml via GitLocalize (#254)
Co-authored-by: András Marczinkó <marczinkoandris@gmail.com>
2022-01-01 18:41:49 -08:00
gitlocalize-app[bot] 322ea825ea
German Translation (#253)
* Translate de.yml via GitLocalize

* Update de.yml

Co-authored-by: Rikamo045 <rik.amos.krajinovic@gmail.com>
Co-authored-by: tastybento <tastybento@users.noreply.github.com>
2022-01-01 18:40:51 -08:00
gitlocalize-app[bot] 488c6ac9d3
Korean translation (#252)
* Translate ko.yml via GitLocalize

* Translate ko.yml via GitLocalize

Co-authored-by: chickiyeah <ruddls030@naver.com>
Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
2022-01-01 18:39:15 -08:00
gitlocalize-app[bot] 840a8c1d79
Translate fr.yml via GitLocalize (#251)
Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>
2022-01-01 18:38:15 -08:00
gitlocalize-app[bot] 34da24d719
Translate id.yml via GitLocalize (#250)
Co-authored-by: Nathan Adhitya <nathanadhitya@outlook.com>
2022-01-01 18:36:45 -08:00
gitlocalize-app[bot] cbaf14e5f0
Chinese Translation (#249)
* Translate zh-CN.yml via GitLocalize

* Translate zh-CN.yml via GitLocalize

Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: 织梦 <493733933@qq.com>
2022-01-01 18:35:59 -08:00
tastybento 7e92a45736
Merge branch 'master' into develop 2021-12-25 08:40:27 -08:00
tastybento 5ce71798a6 Version 2.9.0 2021-12-25 08:39:09 -08:00
Pierre Dedrie 4a21e4b30c
Changed IslandLevelCalculator minHeight to world minHeight for negative blocks height support since 1.18. (#246) 2021-12-18 17:22:28 -08:00
tastybento 50074ac1df
Merge branch 'master' into develop 2021-11-26 13:16:36 -08:00
Rubén cc977d8562
Raw island level placeholder (#241) 2021-11-26 13:11:30 -08:00
Huynh Tien 4de5b80ab4
add Vietnamese (#240) 2021-11-26 13:10:53 -08:00
tastybento 893d8d46a0 Merge branch 'develop' of https://github.com/BentoBoxWorld/Level.git into develop 2021-11-21 20:08:26 -08:00
tastybento b1d117d344 Speeds up level calculation by doing more chunk scans async.
If chests are scanned, then it will take longer because these have to be
done sync.

https://github.com/BentoBoxWorld/Level/issues/243
2021-11-21 20:08:05 -08:00
tastybento 11618085ff Version 2.8.1 2021-11-21 08:14:35 -08:00
tastybento 60f2a268b9
Merge branch 'master' into develop 2021-10-16 16:09:54 -07:00
tastybento 4c59d4d4ae Back support for BentoBox 1.16.5. 2021-10-04 23:01:57 -07:00
tastybento 9b8bbdac5f Open up modules for testing access. 2021-10-01 17:41:39 -07:00
tastybento d212fcee99 Update to BentoBox API 1.18 2021-10-01 17:40:07 -07:00
tastybento 1b29f7f6ac Added new placeholders
%Level_%gamemode%_top_island_name_%rank% - lists the island name
%Level_%gamemode%_top_island_members_%rank% - a comma separated list of
team members

https://github.com/BentoBoxWorld/Level/issues/224
https://github.com/BentoBoxWorld/Level/issues/211
https://github.com/BentoBoxWorld/Level/issues/132
https://github.com/BentoBoxWorld/Level/issues/107
https://github.com/BentoBoxWorld/Level/issues/105
2021-09-06 11:57:32 -07:00
tastybento b16d458cae Version 2.8.0 2021-09-06 11:23:38 -07:00
tastybento 7b6f921b10 Made plugin a Pladdon. 2021-08-15 08:53:16 -07:00
tastybento bd6c264f4d
Rosestacker (#232)
* Add support for RoseStacker 1.3.0
2021-08-09 20:00:55 -07:00
tastybento d55f66f868
No save on disable (#231)
* Release 2.6.4

* Remove saving to database on disable.

https://github.com/BentoBoxWorld/Level/issues/229

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

* Fixes tests
2021-08-08 11:09:36 -07:00
tastybento 76a2688556 Added placeholder %Level_[gamemode]_rank_value
Fixes https://github.com/BentoBoxWorld/Level/issues/228
2021-07-24 14:54:06 -07:00
tastybento 4661bcd109 Use Java 9's takeWhile 2021-07-24 14:26:45 -07:00
tastybento 383ede3d59 Version 2.7.2 2021-07-10 22:04:17 -07:00
tastybento 0ce89dea7f Version 2.7.1 2021-07-03 15:50:37 -07:00
9 changed files with 577 additions and 510 deletions

25
pom.xml
View File

@ -71,7 +71,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.12.0</build.version>
<build.version>2.13.0</build.version>
<sonar.projectKey>BentoBoxWorld_Level</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
@ -129,8 +129,8 @@
<repositories>
<!--Wild Stacker repo -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<id>bg-repo</id>
<url>https://repo.bg-software.com/repository/api/</url>
</repository>
<!-- RoseStacker repo -->
<repository>
@ -139,8 +139,8 @@
</repository>
<!-- UltimateStacker repo -->
<repository>
<id>songoda-public</id>
<url>https://repo.songoda.com/repository/public/</url>
<id>songoda-plugins</id>
<url>https://repo.songoda.com/repository/minecraft-plugins/</url>
</repository>
<repository>
<id>spigot-repo</id>
@ -154,6 +154,10 @@
<id>codemc-public</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
@ -208,9 +212,9 @@
</dependency>
<!-- Wild Stacker dependency -->
<dependency>
<groupId>com.github.OmerBenGera</groupId>
<groupId>com.bgsoftware</groupId>
<artifactId>WildStackerAPI</artifactId>
<version>b18</version>
<version>2023.3</version>
<scope>provided</scope>
</dependency>
<!-- Static analysis -->
@ -234,10 +238,11 @@
<version>1.3.0</version>
<scope>provided</scope>
</dependency>
<!-- Ultimate Stacker dependency -->
<dependency>
<groupId>com.songoda</groupId>
<artifactId>UltimateStacker</artifactId>
<version>2.4.0</version>
<groupId>com.craftaro</groupId>
<artifactId>UltimateStacker-API</artifactId>
<version>1.0.0-20240329.173606-35</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -2,6 +2,7 @@ package world.bentobox.level;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.time.Instant;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
@ -31,6 +32,7 @@ import world.bentobox.level.events.IslandPreLevelEvent;
import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData;
import world.bentobox.level.util.CachedData;
public class LevelsManager {
private static final String INTOPTEN = "intopten";
@ -52,6 +54,8 @@ public class LevelsManager {
private final Map<String, IslandLevels> levelsCache;
// Top ten lists
private final Map<World, TopTenData> topTenLists;
// Cache for top tens
private Map<World, CachedData> cache = new HashMap<>();
public LevelsManager(Level addon) {
this.addon = addon;
@ -66,35 +70,35 @@ public class LevelsManager {
}
public void migrate() {
Database<LevelsData> oldDb = new Database<>(addon, LevelsData.class);
oldDb.loadObjects().forEach(ld -> {
try {
UUID owner = UUID.fromString(ld.getUniqueId());
// Step through each world
ld.getLevels().keySet().stream()
// World
.map(Bukkit::getWorld).filter(Objects::nonNull)
// Island
.map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> {
// Make new database entry
World w = i.getWorld();
IslandLevels il = new IslandLevels(i.getUniqueId());
il.setInitialLevel(ld.getInitialLevel(w));
il.setLevel(ld.getLevel(w));
il.setMdCount(ld.getMdCount(w));
il.setPointsToNextLevel(ld.getPointsToNextLevel(w));
il.setUwCount(ld.getUwCount(w));
// Save it
handler.saveObjectAsync(il);
});
// Now delete the old database entry
oldDb.deleteID(ld.getUniqueId());
} catch (Exception e) {
addon.logError("Could not migrate level data database! " + e.getMessage());
e.printStackTrace();
return;
}
});
Database<LevelsData> oldDb = new Database<>(addon, LevelsData.class);
oldDb.loadObjects().forEach(ld -> {
try {
UUID owner = UUID.fromString(ld.getUniqueId());
// Step through each world
ld.getLevels().keySet().stream()
// World
.map(Bukkit::getWorld).filter(Objects::nonNull)
// Island
.map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> {
// Make new database entry
World w = i.getWorld();
IslandLevels il = new IslandLevels(i.getUniqueId());
il.setInitialLevel(ld.getInitialLevel(w));
il.setLevel(ld.getLevel(w));
il.setMdCount(ld.getMdCount(w));
il.setPointsToNextLevel(ld.getPointsToNextLevel(w));
il.setUwCount(ld.getUwCount(w));
// Save it
handler.saveObjectAsync(il);
});
// Now delete the old database entry
oldDb.deleteID(ld.getUniqueId());
} catch (Exception e) {
addon.logError("Could not migrate level data database! " + e.getMessage());
e.printStackTrace();
return;
}
});
}
/**
@ -105,12 +109,12 @@ public class LevelsManager {
* @return true if successful, false if not added
*/
private boolean addToTopTen(Island island, long lv) {
if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) {
topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen()
.put(island.getUniqueId(), lv);
return true;
}
return false;
if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) {
topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen()
.put(island.getUniqueId(), lv);
return true;
}
return false;
}
/**
@ -122,26 +126,26 @@ public class LevelsManager {
* @return completable future with the results of the calculation
*/
public CompletableFuture<Results> calculateLevel(UUID targetPlayer, Island island) {
CompletableFuture<Results> result = new CompletableFuture<>();
// Fire pre-level calc event
IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island);
Bukkit.getPluginManager().callEvent(e);
if (e.isCancelled()) {
return CompletableFuture.completedFuture(null);
}
// Add island to the pipeline
addon.getPipeliner().addIsland(island).thenAccept(r -> {
// Results are irrelevant because the island is unowned or deleted, or
// IslandLevelCalcEvent is cancelled
if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) {
result.complete(null);
}
// Save result
setIslandResults(island, r);
// Save the island scan details
result.complete(r);
});
return result;
CompletableFuture<Results> result = new CompletableFuture<>();
// Fire pre-level calc event
IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island);
Bukkit.getPluginManager().callEvent(e);
if (e.isCancelled()) {
return CompletableFuture.completedFuture(null);
}
// Add island to the pipeline
addon.getPipeliner().addIsland(island).thenAccept(r -> {
// Results are irrelevant because the island is unowned or deleted, or
// IslandLevelCalcEvent is cancelled
if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) {
result.complete(null);
}
// Save result
setIslandResults(island, r);
// Save the island scan details
result.complete(r);
});
return result;
}
/**
@ -153,19 +157,19 @@ public class LevelsManager {
* @return true if canceled
*/
private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Results results) {
// Fire post calculation event
IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results);
Bukkit.getPluginManager().callEvent(ilce);
if (ilce.isCancelled())
return true;
// Set the values if they were altered
results.setLevel((Long) ilce.getKeyValues().getOrDefault("level", results.getLevel()));
results.setInitialLevel((Long) ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel()));
results.setDeathHandicap((int) ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap()));
results.setPointsToNextLevel(
(Long) ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel()));
results.setTotalPoints((Long) ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints()));
return ((Boolean) ilce.getKeyValues().getOrDefault("isCancelled", false));
// Fire post calculation event
IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results);
Bukkit.getPluginManager().callEvent(ilce);
if (ilce.isCancelled())
return true;
// Set the values if they were altered
results.setLevel((Long) ilce.getKeyValues().getOrDefault("level", results.getLevel()));
results.setInitialLevel((Long) ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel()));
results.setDeathHandicap((int) ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap()));
results.setPointsToNextLevel(
(Long) ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel()));
results.setTotalPoints((Long) ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints()));
return ((Boolean) ilce.getKeyValues().getOrDefault("isCancelled", false));
}
/**
@ -176,25 +180,25 @@ public class LevelsManager {
* @return string of the level.
*/
public String formatLevel(@Nullable Long lvl) {
if (lvl == null)
return "";
String level = String.valueOf(lvl);
// Asking for the level of another player
if (addon.getSettings().isShorthand()) {
BigInteger levelValue = BigInteger.valueOf(lvl);
if (lvl == null)
return "";
String level = String.valueOf(lvl);
// Asking for the level of another player
if (addon.getSettings().isShorthand()) {
BigInteger levelValue = BigInteger.valueOf(lvl);
Map.Entry<BigInteger, String> stage = LEVELS.floorEntry(levelValue);
Map.Entry<BigInteger, String> stage = LEVELS.floorEntry(levelValue);
if (stage != null) { // level > 1000
// 1 052 -> 1.0k
// 1 527 314 -> 1.5M
// 3 874 130 021 -> 3.8G
// 4 002 317 889 -> 4.0T
level = new DecimalFormat("#.#").format(
levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue();
}
}
return level;
if (stage != null) { // level > 1000
// 1 052 -> 1.0k
// 1 527 314 -> 1.5M
// 3 874 130 021 -> 3.8G
// 4 002 317 889 -> 4.0T
level = new DecimalFormat("#.#").format(
levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue();
}
}
return level;
}
/**
@ -216,11 +220,11 @@ public class LevelsManager {
* null
*/
public long getIslandLevel(@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).getLevel();
if (targetPlayer == null)
return 0L;
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getLevel();
}
/**
@ -232,11 +236,11 @@ public class LevelsManager {
* 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();
if (targetPlayer == null)
return 0L;
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getMaxLevel();
}
/**
@ -288,10 +292,10 @@ public class LevelsManager {
* @return string with the number required or blank if the player is unknown
*/
public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null)
return "";
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel());
if (targetPlayer == null)
return "";
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel());
}
/**
@ -304,28 +308,27 @@ public class LevelsManager {
*/
@NonNull
public Map<Island, Long> getWeightedTopTen(@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 -> {
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);
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);
}
/**
@ -338,26 +341,38 @@ public class LevelsManager {
*/
@NonNull
public Map<String, Long> getTopTen(@NonNull World world, int size) {
createAndCleanRankings(world);
// Return the sorted map
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)));
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);
// 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)
.filter(i -> i.getOwner() == null || !hasTopTenPerm(world, i.getOwner())).isPresent());
topTenLists.computeIfAbsent(world, TopTenData::new);
// 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)
.filter(i -> i.getOwner() == null || !hasTopTenPerm(world, i.getOwner())).isPresent());
}
/**
* @return the topTenLists
*/
public Map<World, TopTenData> getTopTenLists() {
return topTenLists;
return topTenLists;
}
/**
@ -368,13 +383,13 @@ public class LevelsManager {
* @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);
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);
}
/**
@ -385,26 +400,26 @@ public class LevelsManager {
* @return true if player has the perm or the player is offline
*/
boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) {
String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world);
return Bukkit.getPlayer(targetPlayer) == null
|| Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world);
return Bukkit.getPlayer(targetPlayer) == null
|| Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
}
/**
* Loads all the top tens from the database
*/
public void loadTopTens() {
topTenLists.clear();
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
addon.log("Generating rankings");
handler.loadObjects().forEach(il -> {
if (il.getLevel() > 0) {
addon.getIslands().getIslandById(il.getUniqueId())
.ifPresent(i -> this.addToTopTen(i, il.getLevel()));
}
});
topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName()));
});
topTenLists.clear();
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
addon.log("Generating rankings");
handler.loadObjects().forEach(il -> {
if (il.getLevel() > 0) {
addon.getIslands().getIslandById(il.getUniqueId())
.ifPresent(i -> this.addToTopTen(i, il.getLevel()));
}
});
topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName()));
});
}
/**
@ -414,10 +429,11 @@ public class LevelsManager {
* @param uuid - the island's uuid
*/
public void removeEntry(World world, String uuid) {
if (topTenLists.containsKey(world)) {
topTenLists.get(world).getTopTen().remove(uuid);
}
if (topTenLists.containsKey(world)) {
topTenLists.get(world).getTopTen().remove(uuid);
// Invalidate the cache because of this deletion
cache.remove(world);
}
}
/**
@ -427,10 +443,10 @@ public class LevelsManager {
* @param lv - initial island level
*/
public void setInitialIslandLevel(@NonNull Island island, long lv) {
if (island.getWorld() == null)
return;
levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv);
handler.saveObjectAsync(levelsCache.get(island.getUniqueId()));
if (island.getWorld() == null)
return;
levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv);
handler.saveObjectAsync(levelsCache.get(island.getUniqueId()));
}
/**
@ -442,21 +458,21 @@ public class LevelsManager {
* @param lv - level
*/
public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
if (island != null) {
String id = island.getUniqueId();
IslandLevels il = levelsCache.computeIfAbsent(id, IslandLevels::new);
// Remove the initial level
if (addon.getSettings().isZeroNewIslandLevels()) {
il.setLevel(lv - il.getInitialLevel());
} else {
il.setLevel(lv);
}
handler.saveObjectAsync(levelsCache.get(id));
// Update TopTen
addToTopTen(island, levelsCache.get(id).getLevel());
}
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
if (island != null) {
String id = island.getUniqueId();
IslandLevels il = levelsCache.computeIfAbsent(id, IslandLevels::new);
// Remove the initial level
if (addon.getSettings().isZeroNewIslandLevels()) {
il.setLevel(lv - il.getInitialLevel());
} else {
il.setLevel(lv);
}
handler.saveObjectAsync(levelsCache.get(id));
// Update TopTen
addToTopTen(island, levelsCache.get(id).getLevel());
}
}
/**
@ -468,18 +484,18 @@ public class LevelsManager {
* @param r - results of the calculation
*/
private void setIslandResults(Island island, Results r) {
if (island == null)
return;
IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new);
ld.setLevel(r.getLevel());
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.setPointsToNextLevel(r.getPointsToNextLevel());
ld.setTotalPoints(r.getTotalPoints());
levelsCache.put(island.getUniqueId(), ld);
handler.saveObjectAsync(ld);
// Update TopTen
addToTopTen(island, ld.getLevel());
if (island == null)
return;
IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new);
ld.setLevel(r.getLevel());
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.setPointsToNextLevel(r.getPointsToNextLevel());
ld.setTotalPoints(r.getTotalPoints());
levelsCache.put(island.getUniqueId(), ld);
handler.saveObjectAsync(ld);
// Update TopTen
addToTopTen(island, ld.getLevel());
}
/**

View File

@ -40,9 +40,6 @@ import com.bgsoftware.wildstacker.api.objects.StackedBarrel;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.core.compatibility.CompatibleMaterial;
import com.songoda.ultimatestacker.stackable.block.BlockStack;
import dev.rosewood.rosestacker.api.RoseStackerAPI;
import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI;
@ -59,13 +56,13 @@ public class IslandLevelCalculator {
private static final String LINE_BREAK = "==================================";
public static final long MAX_AMOUNT = 10000000;
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,
Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX,
Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX,
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,
Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE);
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,
Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX,
Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX,
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,
Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE);
private static final int CHUNKS_TO_SCAN = 100;
private final Level addon;
private final Queue<Pair<Integer, Integer>> chunksToCheck;
@ -129,18 +126,18 @@ public class IslandLevelCalculator {
* @return level of island
*/
private long calculateLevel(long blockAndDeathPoints) {
String calcString = addon.getSettings().getLevelCalc();
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost",
String.valueOf(this.addon.getSettings().getLevelCost()));
long evalWithValues;
try {
evalWithValues = (long) EquationEvaluator.eval(withValues);
return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0);
String calcString = addon.getSettings().getLevelCalc();
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost",
String.valueOf(this.addon.getSettings().getLevelCost()));
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;
}
} catch (ParseException e) {
addon.getPlugin().logStacktrace(e);
return 0L;
}
}
/**
@ -168,15 +165,15 @@ public class IslandLevelCalculator {
* @return - set of pairs of x,z coordinates to check
*/
private Queue<Pair<Integer, Integer>> getChunksToScan(Island island) {
Queue<Pair<Integer, Integer>> chunkQueue = new ConcurrentLinkedQueue<>();
for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2
+ 16); x += 16) {
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2
+ 16); z += 16) {
chunkQueue.add(new Pair<>(x >> 4, z >> 4));
}
}
return chunkQueue;
Queue<Pair<Integer, Integer>> chunkQueue = new ConcurrentLinkedQueue<>();
for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2
+ 16); x += 16) {
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2
+ 16); z += 16) {
chunkQueue.add(new Pair<>(x >> 4, z >> 4));
}
}
return chunkQueue;
}
/**
@ -201,60 +198,60 @@ public class IslandLevelCalculator {
* @return a list of lines
*/
private List<String> getReport() {
List<String> reportLines = new ArrayList<>();
// provide counts
reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld())
+ " at " + Util.xyz(island.getCenter().toVector()));
reportLines.add("Island owner UUID = " + island.getOwner());
reportLines.add("Total block value count = " + String.format("%,d", results.rawBlockCount.get()));
reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc());
reportLines.add("Level cost = " + addon.getSettings().getLevelCost());
reportLines.add("Deaths handicap = " + results.deathHandicap.get());
if (addon.getSettings().isZeroNewIslandLevels()) {
reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island)));
}
reportLines.add("Previous level = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner()));
reportLines.add("New level = " + results.getLevel());
reportLines.add(LINE_BREAK);
int total = 0;
if (!results.uwCount.isEmpty()) {
reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier()
+ ") value");
reportLines.add("Total number of underwater blocks = " + String.format("%,d", results.uwCount.size()));
reportLines.addAll(sortedReport(total, results.uwCount));
}
reportLines.add("Regular block count");
reportLines.add("Total number of blocks = " + String.format("%,d", results.mdCount.size()));
reportLines.addAll(sortedReport(total, results.mdCount));
List<String> reportLines = new ArrayList<>();
// provide counts
reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld())
+ " at " + Util.xyz(island.getCenter().toVector()));
reportLines.add("Island owner UUID = " + island.getOwner());
reportLines.add("Total block value count = " + String.format("%,d", results.rawBlockCount.get()));
reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc());
reportLines.add("Level cost = " + addon.getSettings().getLevelCost());
reportLines.add("Deaths handicap = " + results.deathHandicap.get());
if (addon.getSettings().isZeroNewIslandLevels()) {
reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island)));
}
reportLines.add("Previous level = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner()));
reportLines.add("New level = " + results.getLevel());
reportLines.add(LINE_BREAK);
int total = 0;
if (!results.uwCount.isEmpty()) {
reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier()
+ ") value");
reportLines.add("Total number of underwater blocks = " + String.format("%,d", results.uwCount.size()));
reportLines.addAll(sortedReport(total, results.uwCount));
}
reportLines.add("Regular block count");
reportLines.add("Total number of blocks = " + String.format("%,d", results.mdCount.size()));
reportLines.addAll(sortedReport(total, results.mdCount));
reportLines.add(
"Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size()));
Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet();
Iterator<Entry<Material>> it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement());
String explain = ")";
if (limit == null) {
Material generic = type.getElement();
limit = addon.getBlockConfig().getBlockLimits().get(generic);
explain = " - All types)";
}
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount())
+ " blocks (max " + limit + explain);
}
reportLines.add(LINE_BREAK);
reportLines.add("Blocks on island that are not in config.yml");
reportLines.add("Total number = " + String.format("%,d", results.ncCount.size()));
entriesSortedByCount = results.ncCount.entrySet();
it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks");
}
reportLines.add(LINE_BREAK);
reportLines.add(
"Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size()));
Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet();
Iterator<Entry<Material>> it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement());
String explain = ")";
if (limit == null) {
Material generic = type.getElement();
limit = addon.getBlockConfig().getBlockLimits().get(generic);
explain = " - All types)";
}
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount())
+ " blocks (max " + limit + explain);
}
reportLines.add(LINE_BREAK);
reportLines.add("Blocks on island that are not in config.yml");
reportLines.add("Total number = " + String.format("%,d", results.ncCount.size()));
entriesSortedByCount = results.ncCount.entrySet();
it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks");
}
reportLines.add(LINE_BREAK);
return reportLines;
return reportLines;
}
/**
@ -356,39 +353,39 @@ public class IslandLevelCalculator {
* @param chunk - the chunk to scan
*/
private void scanChests(Chunk chunk) {
// Count blocks in chests
for (BlockState bs : chunk.getTileEntities()) {
if (bs instanceof Container container) {
if (addon.isAdvChestEnabled()) {
AdvancedChest<?, ?> aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation());
if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) {
aChest.getPages().stream().map(ChestPage::getItems).forEach(c -> {
for (Object i : c) {
countItemStack((ItemStack) i);
}
});
continue;
}
}
// Regular chest
container.getSnapshotInventory().forEach(this::countItemStack);
}
}
// Count blocks in chests
for (BlockState bs : chunk.getTileEntities()) {
if (bs instanceof Container container) {
if (addon.isAdvChestEnabled()) {
AdvancedChest<?, ?> aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation());
if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) {
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;
if (i == null || !i.getType().isBlock())
return;
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);
}
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);
}
}
/**
@ -401,26 +398,26 @@ public class IslandLevelCalculator {
* that will be true if the scan was successful, false if not
*/
private CompletableFuture<Boolean> scanChunk(List<Chunk> chunks) {
// If the chunk hasn't been generated, return
if (chunks == null || chunks.isEmpty()) {
return CompletableFuture.completedFuture(false);
}
// Count blocks in chunk
CompletableFuture<Boolean> result = new CompletableFuture<>();
/*
* At this point, we need to grab a snapshot of each chunk and then scan it
* 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;
// If the chunk hasn't been generated, return
if (chunks == null || chunks.isEmpty()) {
return CompletableFuture.completedFuture(false);
}
// Count blocks in chunk
CompletableFuture<Boolean> result = new CompletableFuture<>();
/*
* At this point, we need to grab a snapshot of each chunk and then scan it
* 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;
}
record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
@ -432,67 +429,53 @@ public class IslandLevelCalculator {
* @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);
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 and Spawners Only) - this has to use the real
// chunk
if (addon.isStackersEnabled() && (blockData.getMaterial().equals(Material.CAULDRON)
|| blockData.getMaterial().equals(Material.SPAWNER))) {
stackedBlocks.add(new Location(cp.world, (double) x + cp.chunkSnapshot.getX() * 16, y,
(double) z + cp.chunkSnapshot.getZ() * 16));
}
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));
}
Block block = cp.chunk.getBlock(x, y, z);
if (addon.isUltimateStackerEnabled() && !m.isAir()) {
Location l = new Location(cp.chunk.getWorld(), x, y, z);
UltimateStackerCalc.addStackers(m, l, results, belowSeaLevel, limitCount(m));
}
if (addon.isUltimateStackerEnabled()) {
if (!blockData.getMaterial().equals(Material.AIR)) {
BlockStack stack = UltimateStacker.getInstance().getBlockStackManager().getBlock(block,
CompatibleMaterial.getMaterial(block));
if (stack != null) {
int value = limitCount(blockData.getMaterial());
if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet((long) stack.getAmount() * value);
results.uwCount.add(blockData.getMaterial());
} else {
results.rawBlockCount.addAndGet((long) stack.getAmount() * value);
results.mdCount.add(blockData.getMaterial());
}
}
}
}
// Scan chests
if (addon.getSettings().isIncludeChests() && CHESTS.contains(blockData.getMaterial())) {
chestBlocks.add(cp.chunk);
}
// Add the value of the block's material
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
}
// Scan chests
if (addon.getSettings().isIncludeChests() && CHESTS.contains(m)) {
chestBlocks.add(cp.chunk);
}
// Add the value of the block's material
checkBlock(m, belowSeaLevel);
}
}
}
}
/**
@ -502,94 +485,94 @@ public class IslandLevelCalculator {
* to be scanned, and false if not
*/
public CompletableFuture<Boolean> scanNextChunk() {
if (chunksToCheck.isEmpty()) {
addon.logError("Unexpected: no chunks to scan!");
// This should not be needed, but just in case
return CompletableFuture.completedFuture(false);
}
// Retrieve and remove from the queue
Queue<Pair<Integer, Integer>> pairList = new ConcurrentLinkedQueue<>();
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
CompletableFuture<Boolean> result = new CompletableFuture<>();
// Get chunks and scan
// Get chunks and scan
getWorldChunk(Environment.THE_END, endPairList).thenAccept(
endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList)
.thenAccept(netherChunks -> scanChunk(netherChunks)
.thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList)
.thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 ->
// Complete the result now that all chunks have been scanned
result.complete(!chunksToCheck.isEmpty())))))));
if (chunksToCheck.isEmpty()) {
addon.logError("Unexpected: no chunks to scan!");
// This should not be needed, but just in case
return CompletableFuture.completedFuture(false);
}
// Retrieve and remove from the queue
Queue<Pair<Integer, Integer>> pairList = new ConcurrentLinkedQueue<>();
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
CompletableFuture<Boolean> result = new CompletableFuture<>();
// Get chunks and scan
// Get chunks and scan
getWorldChunk(Environment.THE_END, endPairList).thenAccept(
endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList)
.thenAccept(netherChunks -> scanChunk(netherChunks)
.thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList)
.thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 ->
// Complete the result now that all chunks have been scanned
result.complete(!chunksToCheck.isEmpty())))))));
return result;
return result;
}
private Collection<String> sortedReport(int total, Multiset<Material> materialCount) {
Collection<String> result = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount)
.entrySet();
for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement();
Collection<String> result = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount)
.entrySet();
for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement();
int value = getValue(type);
int value = getValue(type);
result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = "
+ (value * en.getCount()));
total += (value * en.getCount());
result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = "
+ (value * en.getCount()));
total += (value * en.getCount());
}
result.add("Subtotal = " + total);
result.add(LINE_BREAK);
return result;
}
result.add("Subtotal = " + total);
result.add(LINE_BREAK);
return result;
}
/**
* Finalizes the calculations and makes the report
*/
public void tidyUp() {
// Finalize calculations
results.rawBlockCount
.addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
// Finalize calculations
results.rawBlockCount
.addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
// Set the death penalty
if (this.addon.getSettings().isSumTeamDeaths()) {
for (UUID uuid : this.island.getMemberSet()) {
this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid));
}
} else {
// At this point, it may be that the island has become unowned.
this.results.deathHandicap.set(this.island.getOwner() == null ? 0
: this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner()));
}
// Set the death penalty
if (this.addon.getSettings().isSumTeamDeaths()) {
for (UUID uuid : this.island.getMemberSet()) {
this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid));
}
} else {
// At this point, it may be that the island has become unowned.
this.results.deathHandicap.set(this.island.getOwner() == null ? 0
: this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner()));
}
long blockAndDeathPoints = this.results.rawBlockCount.get();
this.results.totalPoints.set(blockAndDeathPoints);
long blockAndDeathPoints = this.results.rawBlockCount.get();
this.results.totalPoints.set(blockAndDeathPoints);
if (this.addon.getSettings().getDeathPenalty() > 0) {
// Proper death penalty calculation.
blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
}
this.results.level.set(calculateLevel(blockAndDeathPoints));
if (this.addon.getSettings().getDeathPenalty() > 0) {
// Proper death penalty calculation.
blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
}
this.results.level.set(calculateLevel(blockAndDeathPoints));
// Calculate how many points are required to get to the next level
long nextLevel = this.results.level.get();
long blocks = blockAndDeathPoints;
while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) {
nextLevel = calculateLevel(++blocks);
}
this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints);
// Calculate how many points are required to get to the next level
long nextLevel = this.results.level.get();
long blocks = blockAndDeathPoints;
while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) {
nextLevel = calculateLevel(++blocks);
}
this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints);
// Report
results.report = getReport();
// Set the duration
addon.getPipeliner().setTime(System.currentTimeMillis() - duration);
// All done.
// Report
results.report = getReport();
// Set the duration
addon.getPipeliner().setTime(System.currentTimeMillis() - duration);
// All done.
}
/**
@ -600,58 +583,58 @@ public class IslandLevelCalculator {
}
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
handleStackedBlocks();
handleChests();
long checkTime = System.currentTimeMillis();
finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> {
// Check every half second if all the chests and stacks have been cleared
if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty())
|| System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
this.tidyUp();
this.getR().complete(getResults());
finishTask.cancel();
}
}, 0, 10L);
// 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
handleStackedBlocks();
handleChests();
long checkTime = System.currentTimeMillis();
finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> {
// Check every half second if all the chests and stacks have been cleared
if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty())
|| System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
this.tidyUp();
this.getR().complete(getResults());
finishTask.cancel();
}
}, 0, 10L);
}
});
}
});
}
private void handleChests() {
Iterator<Chunk> it = chestBlocks.iterator();
while (it.hasNext()) {
Chunk v = it.next();
Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> {
scanChests(c);
it.remove();
});
}
Iterator<Chunk> it = chestBlocks.iterator();
while (it.hasNext()) {
Chunk v = it.next();
Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> {
scanChests(c);
it.remove();
});
}
}
private void handleStackedBlocks() {

View File

@ -0,0 +1,29 @@
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

@ -111,7 +111,11 @@ public class IslandLevelCommand extends CompositeCommand {
}
// Send player how many points are required to reach next island level
if (results.getPointsToNextLevel() >= 0) {
user.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel()));
user.sendMessage("island.level.required-points-to-next-level",
"[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
if (results.getLevel() != oldLevel) {

View File

@ -0,0 +1,30 @@
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

@ -42,8 +42,8 @@ island:
estimated-wait: "&a Estimated wait: [number] seconds"
in-queue: "&a You are number [number] in the queue"
island-level-is: "&a Island level is &b[level]"
required-points-to-next-level: "&a [points] points required until the next level"
deaths: "&c([number] deaths)"
required-points-to-next-level: "&a Level progress: &6 [progress]&b /&e [levelcost] &a points"
deaths: "&c ([number] deaths)"
cooldown: "&c You must wait &b[time] &c seconds until you can do that again"
in-progress: "&6 Island level calculation is in progress..."
time-out: "&c The level calculation took too long. Please try again later."

View File

@ -121,7 +121,7 @@ detail_panel:
# TIPPED_ARROW:INSTANT_DAMAGE:2::LINGER:2
# TIPPED_ARROW:JUMP:2:NOTEXTENDED:NOSPLASH:1
# TIPPED_ARROW:WEAKNESS::::1 - any weakness enchantment
icon: TIPPED_ARROW:INSTANT_HEAL::::1
icon: tipped_arrow{CustomPotionColor:11546150}
title: level.gui.buttons.previous.name
description: level.gui.buttons.previous.description
data:
@ -139,7 +139,7 @@ detail_panel:
7: material_button
8: material_button
9:
icon: TIPPED_ARROW:JUMP::::1
icon: tipped_arrow{CustomPotionColor:8439583}
title: level.gui.buttons.next.name
description: level.gui.buttons.next.description
data:

View File

@ -64,7 +64,7 @@ value_panel:
8: material_button
3:
1:
icon: TIPPED_ARROW:INSTANT_HEAL::::1
icon: tipped_arrow{CustomPotionColor:11546150}
title: level.gui.buttons.previous.name
description: level.gui.buttons.previous.description
data:
@ -82,7 +82,7 @@ value_panel:
7: material_button
8: material_button
9:
icon: TIPPED_ARROW:JUMP::::1
icon: tipped_arrow{CustomPotionColor:8439583}
title: level.gui.buttons.next.name
description: level.gui.buttons.next.description
data: