Compare commits

...

110 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 3061e80097
Release 2.12.0 (#301)
* Version 2.7.1

* Version 2.7.2

* Use Java 9's takeWhile

* Added placeholder %Level_[gamemode]_rank_value

Fixes https://github.com/BentoBoxWorld/Level/issues/228

* No save on disable (#231)

* Release 2.6.4

* Remove saving to database on disable.

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

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

* Fixes tests

* Rosestacker (#232)

* Add support for RoseStacker 1.3.0

* Made plugin a Pladdon.

* Version 2.8.0

* 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

* Update to BentoBox API 1.18

* Open up modules for testing access.

* Back support for BentoBox 1.16.5.

* Version 2.8.1

* 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

* add Vietnamese (#240)

* Raw island level placeholder (#241)

* Changed IslandLevelCalculator minHeight to world minHeight for negative blocks height support since 1.18. (#246)

* Version 2.9.0

* 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>

* Translate id.yml via GitLocalize (#250)

Co-authored-by: Nathan Adhitya <nathanadhitya@outlook.com>

* Translate fr.yml via GitLocalize (#251)

Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>

* 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>

* 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>

* Translate hu.yml via GitLocalize (#254)

Co-authored-by: András Marczinkó <marczinkoandris@gmail.com>

* Version 2.9.1

* Attempt to handle WildStacker spawners

* Fix error lon loading id locale

* Avoid async chunk snapshotting.

Fixes https://github.com/BentoBoxWorld/Level/issues/256

* Update to BentoBox API 1.20.
Replace plugin.yml with spigot-annotations.

Implement customizable TopLevelPanel.

* Fixes some small issues with TopLevelPanel

Add Utils class that contains some useful things.

* Implement customizable DetailsPanel.

Remove old DetailsGUITab due to new implementation.

* Fix failing test.

* Remove blank file

* Added repo for maven plugin snapshots

* 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

* 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

* Update es.yml (#261)

* 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

* Support for AdvancedChests was updated. (#266)

* 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.

* 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

* Translate pl.yml via GitLocalize (#269)

Co-authored-by: wiktorm12 <wiktorm12@gmail.com>

* Translate fr.yml via GitLocalize (#272)

Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>

* Update to Java 17

* Update Github workflow to Java 17

* Adds %Level_[gamemode]_island_level_max% placeholder

This records the lifetime maximum level the island has ever had.
Addresses #271

* Only shows Members or higher in the top members placeholder

Fixes #267

* Add natural log to level-calc formula parsing

Relates to #274

* feat: add island total points + placeholder (#264)

* feat: add island total points + placeholder

* Update IslandLevels.java

* Fix JavaDoc

* Translate zh-CN.yml via GitLocalize (#276)

Co-authored-by: dawnTak <lanlongxiaode@outlook.com>

* Translate nl.yml via GitLocalize (#277)

Co-authored-by: DevSolaris <solaris.dev.2002@gmail.com>

* Add ${argLine} to get jacoco coverage

* Updated Jacoco POM

* Add shulker to in chest count (#275)

* Sonar Cloud code smell clean up (#278)

* 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.

* Remove dependency

* Add UltimateStacker hook for stacked blocks (#281)

* 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

* Updated dependencies

* Version 2.10.1

* Add blocks that should be zero by default as they are available

on the ocean floor. https://github.com/BentoBoxWorld/Level/issues/284

* 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>

* Translate id.yml via GitLocalize (#287)

Co-authored-by: Dusty <siapa-yg-mau-diblokir.kfrxp@simplelogin.com>

* 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>

* 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>

* Version 2.11.0

* Update Github Build script

* Added distribution required for Github Action

* Update Jacoco

* Update pom.xml

* Add config option to disable plugin hooks (#291)

* Update UltimateStacker dependency

* Add config option to disable plugin hooks

* Use 2.0.0 BentoBox API

* Version 2.12.0

* Adds an admin stats command. See #293

* Update tests

* Update to BentoBox 2.0.0 API

* Added test for Stats command

* Try lower version of jacoco

* 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

* 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.

* Translate uk.yml via GitLocalize (#297)

Co-authored-by: GIGABAIT <freebitcoin93@gmail.com>

* Move to 1.20.4

Refactored the calculator code for clarity.

Added Jacoco line to prvent issues with the bigger Material class.

* Added comments on the panel templates.

* Add protection around unknown blockconfig.yml entries. GRASS>SHORT_GRASS

* Uses latest Visit API to avoid chat spam. Fixes #299 (#300)

* Added test class for EquationEvaluator

* Fix merge error

---------

Co-authored-by: Huynh Tien <huynhqtienvtag@gmail.com>
Co-authored-by: Rubén <44579213+Rubenicos@users.noreply.github.com>
Co-authored-by: Pierre Dedrie <Pirgosth74@gmail.com>
Co-authored-by: gitlocalize-app[bot] <55277160+gitlocalize-app[bot]@users.noreply.github.com>
Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: 织梦 <493733933@qq.com>
Co-authored-by: Nathan Adhitya <nathanadhitya@outlook.com>
Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>
Co-authored-by: chickiyeah <ruddls030@naver.com>
Co-authored-by: Rikamo045 <rik.amos.krajinovic@gmail.com>
Co-authored-by: András Marczinkó <marczinkoandris@gmail.com>
Co-authored-by: BONNe <bonne@bonne.id.lv>
Co-authored-by: KrazyxWolf <68208993+KrazyxWolf@users.noreply.github.com>
Co-authored-by: DeadSilenceIV <Barreto-h2@hotmail.com>
Co-authored-by: wiktorm12 <wiktorm12@gmail.com>
Co-authored-by: evlad <emmanuelvlad@gmail.com>
Co-authored-by: dawnTak <lanlongxiaode@outlook.com>
Co-authored-by: DevSolaris <solaris.dev.2002@gmail.com>
Co-authored-by: DevSolaris <105156235+DevSolaris@users.noreply.github.com>
Co-authored-by: ceze88 <dev.ceze@gmail.com>
Co-authored-by: Jeansou <bettertreebot@gmail.com>
Co-authored-by: Dusty <siapa-yg-mau-diblokir.kfrxp@simplelogin.com>
Co-authored-by: Florian CUNY <poslovitch@bentobox.world>
Co-authored-by: ChrissTM03 <criisbr193@gmail.com>
Co-authored-by: Espan <zcraftyt@gmail.com>
Co-authored-by: PapiCapi <49530141+PapiCapi@users.noreply.github.com>
Co-authored-by: GIGABAIT <freebitcoin93@gmail.com>
2024-01-13 19:50:15 -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 f87603de83
Release 2.11.0 (#290)
* Version 2.7.1

* Version 2.7.2

* Use Java 9's takeWhile

* Added placeholder %Level_[gamemode]_rank_value

Fixes https://github.com/BentoBoxWorld/Level/issues/228

* No save on disable (#231)

* Release 2.6.4

* Remove saving to database on disable.

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

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

* Fixes tests

* Rosestacker (#232)

* Add support for RoseStacker 1.3.0

* Made plugin a Pladdon.

* Version 2.8.0

* 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

* Update to BentoBox API 1.18

* Open up modules for testing access.

* Back support for BentoBox 1.16.5.

* Version 2.8.1

* 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

* add Vietnamese (#240)

* Raw island level placeholder (#241)

* Changed IslandLevelCalculator minHeight to world minHeight for negative blocks height support since 1.18. (#246)

* Version 2.9.0

* 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>

* Translate id.yml via GitLocalize (#250)

Co-authored-by: Nathan Adhitya <nathanadhitya@outlook.com>

* Translate fr.yml via GitLocalize (#251)

Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>

* 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>

* 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>

* Translate hu.yml via GitLocalize (#254)

Co-authored-by: András Marczinkó <marczinkoandris@gmail.com>

* Version 2.9.1

* Attempt to handle WildStacker spawners

* Fix error lon loading id locale

* Avoid async chunk snapshotting.

Fixes https://github.com/BentoBoxWorld/Level/issues/256

* Update to BentoBox API 1.20.
Replace plugin.yml with spigot-annotations.

Implement customizable TopLevelPanel.

* Fixes some small issues with TopLevelPanel

Add Utils class that contains some useful things.

* Implement customizable DetailsPanel.

Remove old DetailsGUITab due to new implementation.

* Fix failing test.

* Remove blank file

* Added repo for maven plugin snapshots

* 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

* 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

* Update es.yml (#261)

* 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

* Support for AdvancedChests was updated. (#266)

* 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.

* 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

* Translate pl.yml via GitLocalize (#269)

Co-authored-by: wiktorm12 <wiktorm12@gmail.com>

* Translate fr.yml via GitLocalize (#272)

Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>

* Update to Java 17

* Update Github workflow to Java 17

* Adds %Level_[gamemode]_island_level_max% placeholder

This records the lifetime maximum level the island has ever had.
Addresses #271

* Only shows Members or higher in the top members placeholder

Fixes #267

* Add natural log to level-calc formula parsing

Relates to #274

* feat: add island total points + placeholder (#264)

* feat: add island total points + placeholder

* Update IslandLevels.java

* Fix JavaDoc

* Translate zh-CN.yml via GitLocalize (#276)

Co-authored-by: dawnTak <lanlongxiaode@outlook.com>

* Translate nl.yml via GitLocalize (#277)

Co-authored-by: DevSolaris <solaris.dev.2002@gmail.com>

* Add ${argLine} to get jacoco coverage

* Updated Jacoco POM

* Add shulker to in chest count (#275)

* Sonar Cloud code smell clean up (#278)

* 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.

* Remove dependency

* Add UltimateStacker hook for stacked blocks (#281)

* 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

* Updated dependencies

* Version 2.10.1

* Add blocks that should be zero by default as they are available

on the ocean floor. https://github.com/BentoBoxWorld/Level/issues/284

* 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>

* Translate id.yml via GitLocalize (#287)

Co-authored-by: Dusty <siapa-yg-mau-diblokir.kfrxp@simplelogin.com>

* 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>

* 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>

* Version 2.11.0

---------

Co-authored-by: Huynh Tien <huynhqtienvtag@gmail.com>
Co-authored-by: Rubén <44579213+Rubenicos@users.noreply.github.com>
Co-authored-by: Pierre Dedrie <Pirgosth74@gmail.com>
Co-authored-by: gitlocalize-app[bot] <55277160+gitlocalize-app[bot]@users.noreply.github.com>
Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: 织梦 <493733933@qq.com>
Co-authored-by: Nathan Adhitya <nathanadhitya@outlook.com>
Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com>
Co-authored-by: chickiyeah <ruddls030@naver.com>
Co-authored-by: Rikamo045 <rik.amos.krajinovic@gmail.com>
Co-authored-by: András Marczinkó <marczinkoandris@gmail.com>
Co-authored-by: BONNe <bonne@bonne.id.lv>
Co-authored-by: KrazyxWolf <68208993+KrazyxWolf@users.noreply.github.com>
Co-authored-by: DeadSilenceIV <Barreto-h2@hotmail.com>
Co-authored-by: wiktorm12 <wiktorm12@gmail.com>
Co-authored-by: evlad <emmanuelvlad@gmail.com>
Co-authored-by: dawnTak <lanlongxiaode@outlook.com>
Co-authored-by: DevSolaris <solaris.dev.2002@gmail.com>
Co-authored-by: DevSolaris <105156235+DevSolaris@users.noreply.github.com>
Co-authored-by: ceze88 <dev.ceze@gmail.com>
Co-authored-by: Jeansou <bettertreebot@gmail.com>
Co-authored-by: Dusty <siapa-yg-mau-diblokir.kfrxp@simplelogin.com>
Co-authored-by: Florian CUNY <poslovitch@bentobox.world>
Co-authored-by: ChrissTM03 <criisbr193@gmail.com>
Co-authored-by: Espan <zcraftyt@gmail.com>
2023-06-03 09:17:44 -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
35 changed files with 3735 additions and 2899 deletions

View File

@ -11,21 +11,22 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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@v1
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 17
- name: Cache SonarCloud packages
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

53
pom.xml
View File

@ -58,12 +58,12 @@
<!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version>
<!-- More visible way how to change dependency versions -->
<spigot.version>1.19.4-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>1.23.0</bentobox.version>
<spigot.version>1.20.4-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>2.0.0-SNAPSHOT</bentobox.version>
<!-- Warps addon version -->
<warps.version>1.12.0</warps.version>
<!-- Visit addon version -->
<visit.version>1.4.0</visit.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 -->
@ -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.10.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>
@ -127,6 +127,21 @@
</pluginRepositories>
<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>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
@ -139,21 +154,10 @@
<id>codemc-public</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
<!--Wild Stacker repo -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<!-- RoseStacker repo -->
<repository>
<id>rosewood-repo</id>
<url>https://repo.rosewooddev.io/repository/public/</url>
</repository>
<!-- UltimateStacker repo -->
<repository>
<id>songoda-public</id>
<url>https://repo.songoda.com/repository/public/</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.3.3</version>
<groupId>com.craftaro</groupId>
<artifactId>UltimateStacker-API</artifactId>
<version>1.0.0-20240329.173606-35</version>
<scope>provided</scope>
</dependency>
</dependencies>
@ -331,10 +336,10 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<doclint>none</doclint> <!-- Turnoff all checks -->
<failOnError>false</failOnError>
<additionalJOption>-Xdoclint:none</additionalJOption>
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
<source>16</source>
<source>17</source>
</configuration>
<executions>
<execution>
@ -403,13 +408,15 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<version>0.8.10</version>
<configuration>
<append>true</append>
<excludes>
<!-- This is required to prevent Jacoco from adding
synthetic fields to a JavaBean class (causes errors in testing) -->
<exclude>**/*Names*</exclude>
<!-- Prevents the Material is too large to mock error -->
<exclude>org/bukkit/Material*</exclude>
</excludes>
</configuration>
<executions>

View File

@ -24,6 +24,7 @@ import world.bentobox.level.calculators.Pipeliner;
import world.bentobox.level.commands.AdminLevelCommand;
import world.bentobox.level.commands.AdminLevelStatusCommand;
import world.bentobox.level.commands.AdminSetInitialLevelCommand;
import world.bentobox.level.commands.AdminStatsCommand;
import world.bentobox.level.commands.AdminTopCommand;
import world.bentobox.level.commands.IslandLevelCommand;
import world.bentobox.level.commands.IslandTopCommand;
@ -39,401 +40,412 @@ import world.bentobox.level.requests.TopTenRequestHandler;
import world.bentobox.visit.VisitAddon;
import world.bentobox.warps.Warp;
/**
* @author tastybento
*
*/
public class Level extends Addon {
// The 10 in top ten
public static final int TEN = 10;
// The 10 in top ten
public static final int TEN = 10;
// Settings
private ConfigSettings settings;
private Config<ConfigSettings> configObject = new Config<>(this, ConfigSettings.class);
private BlockConfig blockConfig;
private Pipeliner pipeliner;
private LevelsManager manager;
private boolean stackersEnabled;
private boolean advChestEnabled;
private boolean roseStackersEnabled;
private boolean ultimateStackerEnabled;
private final List<GameModeAddon> registeredGameModes = new ArrayList<>();
// Settings
private ConfigSettings settings;
private Config<ConfigSettings> configObject = new Config<>(this, ConfigSettings.class);
private BlockConfig blockConfig;
private Pipeliner pipeliner;
private LevelsManager manager;
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 warpHook is present.
*/
private Warp warpHook;
/**
* Local variable that stores if visitHook is present.
*/
private VisitAddon visitHook;
/**
* Local variable that stores if visitHook is present.
*/
private VisitAddon visitHook;
@Override
public void onLoad() {
// Save the default config from config.yml
saveDefaultConfig();
if (loadSettings()) {
// Disable
logError("Level settings could not load! Addon disabled.");
setState(State.DISABLED);
} else {
configObject.saveConfigObject(settings);
}
@Override
public void onLoad() {
// Save the default config from config.yml
saveDefaultConfig();
if (loadSettings()) {
// Disable
logError("Level settings could not load! Addon disabled.");
setState(State.DISABLED);
} else {
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);
}
// 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() {
// Load settings again to get worlds
settings = configObject.loadConfigObject();
private boolean loadSettings() {
// Load settings again to get worlds
settings = configObject.loadConfigObject();
return settings == null;
}
return settings == null;
}
@Override
public void onEnable() {
loadBlockSettings();
// Start pipeline
pipeliner = new Pipeliner(this);
// Start Manager
manager = new LevelsManager(this);
// Register listeners
this.registerListener(new IslandActivitiesListeners(this));
this.registerListener(new JoinLeaveListener(this));
this.registerListener(new MigrationListener(this));
@Override
public void onEnable() {
loadBlockSettings();
// Start pipeline
pipeliner = new Pipeliner(this);
// Start Manager
manager = new LevelsManager(this);
// Register listeners
this.registerListener(new IslandActivitiesListeners(this));
this.registerListener(new JoinLeaveListener(this));
this.registerListener(new MigrationListener(this));
// Register commands for GameModes
registeredGameModes.clear();
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> {
log("Level hooking into " + gm.getDescription().getName());
registerCommands(gm);
new PlaceholderManager(this).registerPlaceholders(gm);
registeredGameModes.add(gm);
});
// Register request handlers
registerRequestHandler(new LevelRequestHandler(this));
registerRequestHandler(new TopTenRequestHandler(this));
// Register commands for GameModes
registeredGameModes.clear();
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
log("Level hooking into " + gm.getDescription().getName());
registerCommands(gm);
new PlaceholderManager(this).registerPlaceholders(gm);
registeredGameModes.add(gm);
});
// Register request handlers
registerRequestHandler(new LevelRequestHandler(this));
registerRequestHandler(new TopTenRequestHandler(this));
// Check if WildStackers is enabled on the server
// I only added support for counting blocks into the island level
// Someone else can PR if they want spawners added to the Leveling system :)
if (!settings.getDisabledPluginHooks().contains("WildStacker")) {
stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker");
if (stackersEnabled) {
log("Hooked into WildStackers.");
}
}
// Check if WildStackers is enabled on the server
// I only added support for counting blocks into the island level
// Someone else can PR if they want spawners added to the Leveling system :)
stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker");
if (stackersEnabled) {
log("Hooked into WildStackers.");
}
// Check if AdvancedChests is enabled on the server
Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests");
advChestEnabled = advChest != null;
if (advChestEnabled) {
// Check version
if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) {
log("Hooked into AdvancedChests.");
} else {
logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later");
advChestEnabled = false;
}
}
// Check if RoseStackers is enabled
roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker");
if (roseStackersEnabled) {
log("Hooked into RoseStackers.");
}
// Check if AdvancedChests is enabled on the server
if (!settings.getDisabledPluginHooks().contains("AdvancedChests")) {
Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests");
advChestEnabled = advChest != null;
if (advChestEnabled) {
// Check version
if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) {
log("Hooked into AdvancedChests.");
} else {
logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion()
+ " - requires version 23.0 or later");
advChestEnabled = false;
}
}
}
// Check if UltimateStacker is enabled
ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker");
if (ultimateStackerEnabled) {
log("Hooked into UltimateStacker.");
}
}
// Check if RoseStackers is enabled
if (!settings.getDisabledPluginHooks().contains("RoseStacker")) {
roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker");
if (roseStackersEnabled) {
log("Hooked into RoseStackers.");
}
}
@Override
public void allLoaded()
{
super.allLoaded();
// Check if UltimateStacker is enabled
if (!settings.getDisabledPluginHooks().contains("UltimateStacker")) {
ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker");
if (ultimateStackerEnabled) {
log("Hooked into UltimateStacker.");
}
}
}
if (this.isEnabled())
{
this.hookExtensions();
}
}
@Override
public void allLoaded() {
super.allLoaded();
if (this.isEnabled()) {
this.hookExtensions();
}
}
/**
* This method tries to hook into addons and plugins
*/
private void hookExtensions()
{
// Try to find Visit addon and if it does not exist, display a warning
this.getAddonByName("Visit").ifPresentOrElse(addon ->
{
this.visitHook = (VisitAddon) addon;
this.log("Level Addon hooked into Visit addon.");
}, () -> this.visitHook = null);
/**
* This method tries to hook into addons and plugins
*/
private void hookExtensions() {
// Try to find Visit addon and if it does not exist, display a warning
this.getAddonByName("Visit").ifPresentOrElse(addon -> {
this.visitHook = (VisitAddon) addon;
this.log("Level Addon hooked into Visit addon.");
}, () -> this.visitHook = null);
// Try to find Warps addon and if it does not exist, display a warning
this.getAddonByName("Warps").ifPresentOrElse(addon ->
{
this.warpHook = (Warp) addon;
this.log("Level Addon hooked into Warps addon.");
}, () -> this.warpHook = null);
}
// Try to find Warps addon and if it does not exist, display a warning
this.getAddonByName("Warps").ifPresentOrElse(addon -> {
this.warpHook = (Warp) addon;
this.log("Level Addon hooked into Warps addon.");
}, () -> this.warpHook = null);
}
/**
* Compares versions
*
* @param version1 version 1
* @param version2 version 2
* @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;
/**
* Compares versions
* @param version1
* @param version2
* @return <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);
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;
}
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) {
gm.getAdminCommand().ifPresent(adminCommand -> {
new AdminLevelCommand(this, adminCommand);
new AdminTopCommand(this, adminCommand);
new AdminLevelStatusCommand(this, adminCommand);
if (getSettings().isZeroNewIslandLevels()) {
new AdminSetInitialLevelCommand(this, adminCommand);
}
new AdminStatsCommand(this, adminCommand);
});
gm.getPlayerCommand().ifPresent(playerCmd -> {
new IslandLevelCommand(this, playerCmd);
new IslandTopCommand(this, playerCmd);
new IslandValueCommand(this, playerCmd);
});
}
private void registerCommands(GameModeAddon gm) {
gm.getAdminCommand().ifPresent(adminCommand -> {
new AdminLevelCommand(this, adminCommand);
new AdminTopCommand(this, adminCommand);
new AdminLevelStatusCommand(this, adminCommand);
if (getSettings().isZeroNewIslandLevels()) {
new AdminSetInitialLevelCommand(this, adminCommand);
}
});
gm.getPlayerCommand().ifPresent(playerCmd -> {
new IslandLevelCommand(this, playerCmd);
new IslandTopCommand(this, playerCmd);
new IslandValueCommand(this, playerCmd);
});
}
@Override
public void onDisable() {
// Stop the pipeline
this.getPipeliner().stop();
}
@Override
public void onDisable() {
// Stop the pipeline
this.getPipeliner().stop();
}
private void loadBlockSettings() {
// Save the default blockconfig.yml
this.saveResource("blockconfig.yml", false);
private void loadBlockSettings() {
// Save the default blockconfig.yml
this.saveResource("blockconfig.yml", false);
YamlConfiguration blockValues = new YamlConfiguration();
try {
File file = new File(this.getDataFolder(), "blockconfig.yml");
blockValues.load(file);
// Load the block config class
blockConfig = new BlockConfig(this, blockValues, file);
} catch (IOException | InvalidConfigurationException e) {
// Disable
logError("Level blockconfig.yml settings could not load! Addon disabled.");
setState(State.DISABLED);
}
YamlConfiguration blockValues = new YamlConfiguration();
try {
File file = new File(this.getDataFolder(), "blockconfig.yml");
blockValues.load(file);
// Load the block config class
blockConfig = new BlockConfig(this, blockValues, file);
} catch (IOException | InvalidConfigurationException e) {
// Disable
logError("Level blockconfig.yml settings could not load! Addon disabled.");
setState(State.DISABLED);
}
}
}
/**
* @return the blockConfig
*/
public BlockConfig getBlockConfig() {
return blockConfig;
}
/**
* @return the settings
*/
public ConfigSettings getSettings() {
return settings;
}
/**
* @return the blockConfig
*/
public BlockConfig getBlockConfig() {
return blockConfig;
}
/**
* @return the pipeliner
*/
public Pipeliner getPipeliner() {
return pipeliner;
}
/**
* @return the settings
*/
public ConfigSettings getSettings() {
return settings;
}
/**
* @return the manager
*/
public LevelsManager getManager() {
return manager;
}
/**
* @return the pipeliner
*/
public Pipeliner getPipeliner() {
return pipeliner;
}
/**
* Set the config settings - used for tests only
*
* @param configSettings - config settings
*/
void setSettings(ConfigSettings configSettings) {
this.settings = configSettings;
/**
* @return the manager
*/
public LevelsManager getManager() {
return manager;
}
}
/**
* Set the config settings - used for tests only
* @param configSettings - config settings
*/
void setSettings(ConfigSettings configSettings) {
this.settings = configSettings;
/**
* @return the stackersEnabled
*/
public boolean isStackersEnabled() {
return stackersEnabled;
}
}
/**
* @return the advChestEnabled
*/
public boolean isAdvChestEnabled() {
return advChestEnabled;
}
/**
* @return the stackersEnabled
*/
public boolean isStackersEnabled() {
return stackersEnabled;
}
/**
* Get level from cache for a player.
*
* @param targetPlayer - target player UUID
* @return Level of player or zero if player is unknown or UUID is null
*/
public long getIslandLevel(World world, @Nullable UUID targetPlayer) {
return getManager().getIslandLevel(world, targetPlayer);
}
/**
* @return the advChestEnabled
*/
public boolean isAdvChestEnabled() {
return advChestEnabled;
}
/**
* Sets the player's level to a value
*
* @param world - world
* @param targetPlayer - target player
* @param level - level
*/
public void setIslandLevel(World world, UUID targetPlayer, long level) {
getManager().setIslandLevel(world, targetPlayer, level);
}
/**
* Get level from cache for a player.
* @param targetPlayer - target player UUID
* @return Level of player or zero if player is unknown or UUID is null
*/
public long getIslandLevel(World world, @Nullable UUID targetPlayer) {
return getManager().getIslandLevel(world, targetPlayer);
}
/**
* Zeros the initial island level
*
* @param island - island
* @param level - initial calculated island level
*/
public void setInitialIslandLevel(@NonNull Island island, long level) {
getManager().setInitialIslandLevel(island, level);
}
/**
* Sets the player's level to a value
* @param world - world
* @param targetPlayer - target player
* @param level - level
*/
public void setIslandLevel(World world, UUID targetPlayer, long level) {
getManager().setIslandLevel(world, targetPlayer, level);
}
/**
* Get the initial island level
*
* @param island - island
* @return level or 0 by default
*/
public long getInitialIslandLevel(@NonNull Island island) {
return getManager().getInitialLevel(island);
}
/**
* Zeros the initial island level
* @param island - island
* @param level - initial calculated island level
*/
public void setInitialIslandLevel(@NonNull Island island, long level) {
getManager().setInitialIslandLevel(island, level);
}
/**
* Calculates a user's island
*
* @param world - the world where this island is
* @param user - not used! See depecration message
* @param playerUUID - the target island member's UUID
* @deprecated Do not use this anymore. Use
* getManager().calculateLevel(playerUUID, island)
*/
@Deprecated(since = "2.3.0", forRemoval = true)
public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) {
Island island = getIslands().getIsland(world, playerUUID);
if (island != null)
getManager().calculateLevel(playerUUID, island);
}
/**
* Get the initial island level
* @param island - island
* @return level or 0 by default
*/
public long getInitialIslandLevel(@NonNull Island island) {
return getManager().getInitialLevel(island);
}
/**
* Provide the levels data for the target player
*
* @param targetPlayer - UUID of target player
* @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(since = "2.3.0", forRemoval = true)
public LevelsData getLevelsData(UUID targetPlayer) {
LevelsData ld = new LevelsData(targetPlayer);
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> {
if (getSettings().isZeroNewIslandLevels()) {
Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer);
if (island != null) {
ld.setInitialLevel(gm.getOverWorld(), this.getInitialIslandLevel(island));
}
}
ld.setLevel(gm.getOverWorld(), this.getIslandLevel(gm.getOverWorld(), targetPlayer));
});
return ld;
}
/**
* Calculates a user's island
* @param world - the world where this island is
* @param user - not used! See depecration message
* @param playerUUID - the target island member's UUID
* @deprecated Do not use this anymore. Use getManager().calculateLevel(playerUUID, island)
*/
@Deprecated(since="2.3.0", forRemoval=true)
public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) {
Island island = getIslands().getIsland(world, playerUUID);
if (island != null) getManager().calculateLevel(playerUUID, island);
}
/**
* @return the registeredGameModes
*/
public List<GameModeAddon> getRegisteredGameModes() {
return registeredGameModes;
}
/**
* Provide the levels data for the target player
* @param targetPlayer - UUID of target player
* @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(since="2.3.0", forRemoval=true)
public LevelsData getLevelsData(UUID targetPlayer) {
LevelsData ld = new LevelsData(targetPlayer);
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
if (getSettings().isZeroNewIslandLevels()) {
Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer);
if (island != null) {
ld.setInitialLevel(gm.getOverWorld(), this.getInitialIslandLevel(island));
}
}
ld.setLevel(gm.getOverWorld(), this.getIslandLevel(gm.getOverWorld(), targetPlayer));
});
return ld;
}
/**
* 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);
}
/**
* @return the registeredGameModes
*/
public List<GameModeAddon> getRegisteredGameModes() {
return registeredGameModes;
}
/**
* 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));
}
/**
* 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);
}
/**
* @return the roseStackersEnabled
*/
public boolean isRoseStackersEnabled() {
return roseStackersEnabled;
}
/**
* 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 ultimateStackerEnabled
*/
public boolean isUltimateStackerEnabled() {
return ultimateStackerEnabled;
}
/**
* @return the roseStackersEnabled
*/
public boolean isRoseStackersEnabled() {
return roseStackersEnabled;
}
/**
* Method Level#getVisitHook returns the visitHook of this object.
*
* @return {@code Visit} of this object, {@code null} otherwise.
*/
public VisitAddon getVisitHook() {
return this.visitHook;
}
/**
* @return the ultimateStackerEnabled
*/
public boolean isUltimateStackerEnabled() {
return ultimateStackerEnabled;
}
/**
* Method Level#getWarpHook returns the warpHook of this object.
*
* @return {@code Warp} of this object, {@code null} otherwise.
*/
public Warp getWarpHook() {
return this.warpHook;
}
/**
* 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

@ -2,6 +2,8 @@ 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;
import java.util.LinkedHashMap;
@ -30,19 +32,19 @@ 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";
private static final TreeMap<BigInteger, String> LEVELS;
private static final BigInteger THOUSAND = BigInteger.valueOf(1000);
static {
LEVELS = new TreeMap<>();
LEVELS = new TreeMap<>();
LEVELS.put(THOUSAND, "k");
LEVELS.put(THOUSAND.pow(2), "M");
LEVELS.put(THOUSAND.pow(3), "G");
LEVELS.put(THOUSAND.pow(4), "T");
LEVELS.put(THOUSAND, "k");
LEVELS.put(THOUSAND.pow(2), "M");
LEVELS.put(THOUSAND.pow(3), "G");
LEVELS.put(THOUSAND.pow(4), "T");
}
private final Level addon;
@ -51,19 +53,20 @@ public class LevelsManager {
// A cache of island levels.
private final Map<String, IslandLevels> levelsCache;
// Top ten lists
private final Map<World,TopTenData> topTenLists;
private final Map<World, TopTenData> topTenLists;
// Cache for top tens
private Map<World, CachedData> cache = new HashMap<>();
public LevelsManager(Level addon) {
this.addon = addon;
// Get the BentoBox database
// Set up the database handler to store and retrieve data
// Note that these are saved by the BentoBox database
handler = new Database<>(addon, IslandLevels.class);
// Initialize the cache
levelsCache = new HashMap<>();
// Initialize top ten lists
topTenLists = new ConcurrentHashMap<>();
this.addon = addon;
// Get the BentoBox database
// Set up the database handler to store and retrieve data
// Note that these are saved by the BentoBox database
handler = new Database<>(addon, IslandLevels.class);
// Initialize the cache
levelsCache = new HashMap<>();
// Initialize top ten lists
topTenLists = new ConcurrentHashMap<>();
}
public void migrate() {
@ -73,22 +76,21 @@ public class LevelsManager {
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);
});
// 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) {
@ -99,45 +101,28 @@ 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
*
* @param island - island to add
* @param lv - level
* @param lv - level
* @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.getOwner(), lv);
topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen()
.put(island.getUniqueId(), lv);
return true;
}
return false;
}
/**
* Calculate the island level, set all island member's levels to the result and try to add the owner to the top ten
* Calculate the island level, set all island member's levels to the result and
* try to add the owner to the top ten
*
* @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
*/
public CompletableFuture<Results> calculateLevel(UUID targetPlayer, Island island) {
@ -150,12 +135,13 @@ public class LevelsManager {
}
// 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
// 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.getWorld(), island.getOwner(), r);
setIslandResults(island, r);
// Save the island scan details
result.complete(r);
});
@ -164,35 +150,41 @@ public class LevelsManager {
/**
* Fires the IslandLevelCalculatedEvent and returns true if it is canceled
*
* @param targetPlayer - target player
* @param island - island
* @param results - results set
* @param island - island
* @param results - results set
* @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;
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));
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));
}
/**
* Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k
* Get the string representation of the level. May be converted to shorthand
* notation, e.g., 104556 = 10.5k
*
* @param lvl - long value to represent
* @return string of the level.
*/
public String formatLevel(@Nullable Long lvl) {
if (lvl == null) return "";
if (lvl == null)
return "";
String level = String.valueOf(lvl);
// Asking for the level of another player
if(addon.getSettings().isShorthand()) {
if (addon.getSettings().isShorthand()) {
BigInteger levelValue = BigInteger.valueOf(lvl);
Map.Entry<BigInteger, String> stage = LEVELS.floorEntry(levelValue);
@ -202,7 +194,8 @@ public class LevelsManager {
// 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();
level = new DecimalFormat("#.#").format(
levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue();
}
}
return level;
@ -210,21 +203,25 @@ public class LevelsManager {
/**
* Get the initial level of the island. Used to zero island levels
*
* @param island - island
* @return initial level of island
*/
public long getInitialLevel(Island island) {
return getLevelsData(island).getInitialLevel();
return getLevelsData(island).getInitialLevel();
}
/**
* 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
* @return Level of the player's island or zero if player is unknown or UUID is null
* @return Level of the player's island or zero if player is unknown or UUID is
* null
*/
public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return 0L;
if (targetPlayer == null)
return 0L;
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getLevel();
@ -232,12 +229,15 @@ public class LevelsManager {
/**
* Get the maximum level ever given to this island
* @param world - world where the island is
*
* @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
* @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;
if (targetPlayer == null)
return 0L;
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getMaxLevel();
@ -245,108 +245,164 @@ public class LevelsManager {
/**
* 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
* @return Formatted level of player or zero if player is unknown or UUID is null
* @return Formatted level of player or zero if player is unknown or UUID is
* null
*/
public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) {
return formatLevel(getIslandLevel(world, targetPlayer));
return formatLevel(getIslandLevel(world, targetPlayer));
}
/**
* Load a level data for the island from the cache or database.
*
* @param island - UUID of island
* @return IslandLevels object
*/
@NonNull
public IslandLevels getLevelsData(@NonNull Island island) {
String id = island.getUniqueId();
if (levelsCache.containsKey(id)) {
return levelsCache.get(id);
}
// Get from database if not in cache
if (handler.objectExists(id)) {
IslandLevels ld = handler.loadObject(id);
if (ld != null) {
levelsCache.put(id, ld);
} else {
handler.deleteID(id);
levelsCache.put(id, new IslandLevels(id));
}
} else {
levelsCache.put(id, new IslandLevels(id));
}
// Return cached value
return levelsCache.get(id);
String id = island.getUniqueId();
if (levelsCache.containsKey(id)) {
return levelsCache.get(id);
}
// Get from database if not in cache
if (handler.objectExists(id)) {
IslandLevels ld = handler.loadObject(id);
if (ld != null) {
levelsCache.put(id, ld);
} else {
handler.deleteID(id);
levelsCache.put(id, new IslandLevels(id));
}
} else {
levelsCache.put(id, new IslandLevels(id));
}
// Return cached value
return levelsCache.get(id);
}
/**
* Get the number of points required until the next level since the last level calc
* @param world - world where the island is
* Get the number of points required until the next level since the last level
* calc
*
* @param world - world where the island is
* @param targetPlayer - target player UUID
* @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 "";
if (targetPlayer == null)
return "";
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel());
}
/**
* Get the top ten for this world. Returns offline players or players with the intopten permission.
* Get the weighted top ten for this world. Weighting is based on number of
* players per team.
*
* @param world - world requested
* @param size - size of the top ten
* @return sorted top ten map
* @param size - size of the top ten
* @return sorted top ten map. The key is the island unique ID
*/
@NonNull
public Map<UUID, Long> getTopTen(@NonNull World world, int size) {
public Map<Island, Long> getWeightedTopTen(@NonNull World world, int size) {
createAndCleanRankings(world);
// Return the sorted map
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(e -> addon.getIslands().isOwner(world, e.getKey()))
.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)));
.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 -> !hasTopTenPerm(world, u));
topTenLists.get(world).getTopTen().keySet().removeIf(u -> addon.getIslands().getIslandById(u)
.filter(i -> i.getOwner() == null || !hasTopTenPerm(world, i.getOwner())).isPresent());
}
/**
* @return the topTenLists
*/
protected Map<World, TopTenData> getTopTenLists() {
public Map<World, TopTenData> getTopTenLists() {
return topTenLists;
}
/**
* Get the rank of the player in the rankings
*
* @param world - world
* @param uuid - player UUID
* @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<UUID, Long>> stream = topTenLists.get(world).getTopTen().entrySet().stream()
.filter(e -> addon.getIslands().isOwner(world, e.getKey()))
.filter(l -> l.getValue() > 0)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1);
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
*
* @param world
* @param targetPlayer
* @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);
return Bukkit.getPlayer(targetPlayer) == null
|| Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
}
/**
@ -358,7 +414,8 @@ public class LevelsManager {
addon.log("Generating rankings");
handler.loadObjects().forEach(il -> {
if (il.getLevel() > 0) {
addon.getIslands().getIslandById(il.getUniqueId()).ifPresent(i -> this.addToTopTen(i, il.getLevel()));
addon.getIslands().getIslandById(il.getUniqueId())
.ifPresent(i -> this.addToTopTen(i, il.getLevel()));
}
});
topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName()));
@ -366,33 +423,39 @@ public class LevelsManager {
}
/**
* Removes a player from a world's top ten and removes world from player's level data
* Removes an island from a world's top ten
*
* @param world - world
* @param uuid - the player's uuid
* @param uuid - the island's uuid
*/
public void removeEntry(World world, UUID uuid) {
if (topTenLists.containsKey(world)) {
public void removeEntry(World world, String uuid) {
if (topTenLists.containsKey(world)) {
topTenLists.get(world).getTopTen().remove(uuid);
// Invalidate the cache because of this deletion
cache.remove(world);
}
}
/**
* Set an initial island level
*
* @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) {
if (island.getWorld() == null) return;
if (island.getWorld() == null)
return;
levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv);
handler.saveObjectAsync(levelsCache.get(island.getUniqueId()));
}
/**
* Set the island level for the owner of the island that targetPlayer is a member
* @param world - world
* @param targetPlayer - player, may be a team member
* @param lv - level
* Set the island level for the owner of the island that targetPlayer is a
* member
*
* @param world - world
* @param island - island
* @param lv - level
*/
public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
// Get the island
@ -408,21 +471,21 @@ public class LevelsManager {
}
handler.saveObjectAsync(levelsCache.get(id));
// Update TopTen
addToTopTen(world, targetPlayer, levelsCache.get(id).getLevel());
addToTopTen(island, levelsCache.get(id).getLevel());
}
}
/**
* Set the island level for the owner of the island that targetPlayer is a member
* Set the island level for the owner of the island that targetPlayer is a
* member
*
* @param world - world
* @param owner - owner of the island
* @param r - results of the calculation
* @param r - results of the calculation
*/
private void setIslandResults(World world, @NonNull UUID owner, Results r) {
// Get the island
Island island = addon.getIslands().getIsland(world, owner);
if (island == null) return;
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)));
@ -432,16 +495,17 @@ public class LevelsManager {
levelsCache.put(island.getUniqueId(), ld);
handler.saveObjectAsync(ld);
// Update TopTen
addToTopTen(world, owner, ld.getLevel());
addToTopTen(island, ld.getLevel());
}
/**
* Removes island from cache when it is deleted
*
* @param uniqueId - id of island
*/
public void deleteIsland(String uniqueId) {
levelsCache.remove(uniqueId);
handler.deleteID(uniqueId);
levelsCache.remove(uniqueId);
handler.deleteID(uniqueId);
}
}

View File

@ -2,10 +2,12 @@ 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;
@ -18,6 +20,7 @@ import world.bentobox.level.objects.TopTenData;
/**
* Handles Level placeholders
*
* @author tastybento
*
*/
@ -27,150 +30,186 @@ public class PlaceholderManager {
private final BentoBox plugin;
public PlaceholderManager(Level addon) {
this.addon = addon;
this.plugin = addon.getPlugin();
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()));
bpm.registerPlaceholder(addon,
gm.getDescription().getName().toLowerCase() + "_island_level_raw",
user -> String.valueOf(addon.getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId())));
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()+"";
});
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())));
bpm.registerPlaceholder(addon,
gm.getDescription().getName().toLowerCase() + "_points_to_next_level",
user -> addon.getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId()));
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));
// 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));
}
// 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));
// Island Name
bpm.registerPlaceholder(addon,
gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i, u -> getRankIslandName(gm.getOverWorld(), rank));
// Members
bpm.registerPlaceholder(addon,
gm.getDescription().getName().toLowerCase() + "_top_members_" + i, u -> getRankMembers(gm.getOverWorld(), rank));
// Level
bpm.registerPlaceholder(addon,
gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank));
}
// Personal rank
bpm.registerPlaceholder(addon,
gm.getDescription().getName().toLowerCase() + "_rank_value", u -> getRankValue(gm.getOverWorld(), u));
// Personal rank
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_rank_value",
u -> getRankValue(gm.getOverWorld(), u));
}
/**
* Get the name of the player who holds the rank in this world
* @param world world
* @param rank rank 1 to 10
* 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) {
if (rank < 1) rank = 1;
if (rank > Level.TEN) rank = Level.TEN;
return addon.getPlayers().getName(addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null));
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 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) {
if (rank < 1) rank = 1;
if (rank > Level.TEN) rank = Level.TEN;
UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null);
if (owner != null) {
Island island = addon.getIslands().getIsland(world, owner);
if (island != null) {
return island.getName() == null ? "" : island.getName();
}
}
return "";
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 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) {
if (rank < 1) rank = 1;
if (rank > Level.TEN) rank = Level.TEN;
UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null);
if (owner != null) {
Island island = addon.getIslands().getIsland(world, owner);
if (island != null) {
// Sort members by rank
return island.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 "";
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 "";
}
String getRankLevel(World world, int rank) {
if (rank < 1) rank = 1;
if (rank > Level.TEN) rank = Level.TEN;
return addon.getManager()
.formatLevel(addon.getManager()
.getTopTen(world, Level.TEN)
.values()
.stream()
.skip(rank - 1L)
.limit(1L)
.findFirst()
.orElse(null));
/**
* 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
* @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);
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");
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

@ -0,0 +1,121 @@
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,6 +1,6 @@
package world.bentobox.level.calculators;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -16,9 +16,6 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.core.compatibility.CompatibleMaterial;
import com.songoda.ultimatestacker.stackable.block.BlockStack;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
@ -27,7 +24,11 @@ import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.block.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
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.type.Slab;
import org.bukkit.inventory.ItemStack;
@ -54,121 +55,21 @@ import world.bentobox.level.calculators.Results.Result;
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,
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.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;
/**
* Method to evaluate a mathematical equation
* @param str - equation to evaluate
* @return value of equation
*/
private static double eval(final String str) throws IOException {
return new Object() {
int pos = -1;
int 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() throws IOException {
nextChar();
double x = parseExpression();
if (pos < str.length()) throw new IOException("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() throws IOException {
double x = parseTerm();
for (;;) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseFactor() throws IOException {
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;
case "log":
x = Math.log(x);
break;
default:
throw new IOException("Unknown function: " + func);
}
} else {
throw new IOException("Unexpected: " + (char)ch);
}
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
double parseTerm() throws IOException {
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 Queue<Pair<Integer, Integer>> chunksToCheck;
private final Island island;
private final Map<Material, Integer> limitCount;
private final CompletableFuture<Results> r;
private final Results results;
private long duration;
private final boolean zeroIsland;
@ -178,121 +79,131 @@ public class IslandLevelCalculator {
private final Set<Chunk> chestBlocks = new HashSet<>();
private BukkitTask finishTask;
/**
* Constructor to get the level for an island
* @param addon - Level addon
* @param island - the island to scan
* @param r - completable result that will be completed when the calculation is complete
*
* @param addon - Level addon
* @param island - the island to scan
* @param r - completable result that will be completed when the
* calculation is complete
* @param zeroIsland - true if the calculation is due to an island zeroing
*/
public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r, boolean zeroIsland) {
this.addon = addon;
this.island = island;
this.r = r;
this.zeroIsland = zeroIsland;
results = new Results();
duration = System.currentTimeMillis();
chunksToCheck = getChunksToScan(island);
this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits());
// Get the initial island level
results.initialLevel.set(addon.getInitialIslandLevel(island));
// Set up the worlds
worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld()));
// Nether
if (addon.getSettings().isNether()) {
World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
if (nether != null) {
worlds.put(Environment.NETHER, nether);
}
}
// End
if (addon.getSettings().isEnd()) {
World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
if (end != null) {
worlds.put(Environment.THE_END, end);
}
}
// Sea Height
seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld());
this.addon = addon;
this.island = island;
this.r = r;
this.zeroIsland = zeroIsland;
results = new Results();
duration = System.currentTimeMillis();
chunksToCheck = getChunksToScan(island);
this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits());
// Get the initial island level
results.initialLevel.set(addon.getInitialIslandLevel(island));
// Set up the worlds
worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld()));
// Nether
if (addon.getSettings().isNether()) {
World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
if (nether != null) {
worlds.put(Environment.NETHER, nether);
}
}
// End
if (addon.getSettings().isEnd()) {
World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
if (end != null) {
worlds.put(Environment.THE_END, end);
}
}
// Sea Height
seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld());
}
/**
* Calculate the level based on the raw points
*
* @param blockAndDeathPoints - raw points counted on island
* @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()));
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost",
String.valueOf(this.addon.getSettings().getLevelCost()));
long evalWithValues;
try {
evalWithValues = (long)eval(withValues);
evalWithValues = (long) EquationEvaluator.eval(withValues);
return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0);
} catch (IOException e) {
} catch (ParseException e) {
addon.getPlugin().logStacktrace(e);
return 0L;
}
}
/**
* Adds value to the results based on the material and whether the block is below sea level or not
* @param mat - material of the block
* Adds value to the results based on the material and whether the block is
* below sea level or not
*
* @param mat - material of the block
* @param belowSeaLevel - true if below sea level
*/
private void checkBlock(Material mat, boolean belowSeaLevel) {
int count = limitCount(mat);
if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet(count);
results.uwCount.add(mat);
} else {
results.rawBlockCount.addAndGet(count);
results.mdCount.add(mat);
}
int count = limitCount(mat);
if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet(count);
results.uwCount.add(mat);
} else {
results.rawBlockCount.addAndGet(count);
results.mdCount.add(mat);
}
}
/**
* Get a set of all the chunks in island
*
* @param island - island
* @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) {
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;
}
/**
* @return the island
*/
public Island getIsland() {
return island;
return island;
}
/**
* Get the completable result for this calculation
*
* @return the r
*/
public CompletableFuture<Results> getR() {
return r;
return r;
}
/**
* Get the full analysis report
*
* @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("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("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());
@ -304,15 +215,17 @@ public class IslandLevelCalculator {
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.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.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()));
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()) {
@ -324,16 +237,17 @@ public class IslandLevelCalculator {
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(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()));
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(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks");
}
reportLines.add(LINE_BREAK);
@ -344,93 +258,98 @@ public class IslandLevelCalculator {
* @return the results
*/
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
* @return value of a material
*/
private int getValue(Material md) {
Integer value = addon.getBlockConfig().getValue(island.getWorld(), md);
if (value == null) {
// Not in config
results.ncCount.add(md);
return 0;
}
return value;
Integer value = addon.getBlockConfig().getValue(island.getWorld(), md);
if (value == null) {
// Not in config
results.ncCount.add(md);
return 0;
}
return value;
}
/**
* Get a chunk async
* @param env - the environment
*
* @param env - the environment
* @param pairList - chunk coordinate
* @return a future chunk or future null if there is no chunk to load, e.g., 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) {
if (worlds.containsKey(env)) {
CompletableFuture<List<Chunk>> r2 = new CompletableFuture<>();
List<Chunk> chunkList = new ArrayList<>();
World world = worlds.get(env);
// Get the chunk, and then coincidentally check the RoseStacker
loadChunks(r2, world, pairList, chunkList);
return r2;
}
return CompletableFuture.completedFuture(Collections.emptyList());
if (worlds.containsKey(env)) {
CompletableFuture<List<Chunk>> r2 = new CompletableFuture<>();
List<Chunk> chunkList = new ArrayList<>();
World world = worlds.get(env);
// Get the chunk, and then coincidentally check the RoseStacker
loadChunks(r2, world, pairList, chunkList);
return r2;
}
return CompletableFuture.completedFuture(Collections.emptyList());
}
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
});
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);
}
});
}
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 or not
* Checks if a block has been limited or not and whether a block has any value
* or not
*
* @param md Material
* @return value of the block if can be counted
*/
private int limitCount(Material md) {
if (limitCount.containsKey(md)) {
int count = limitCount.get(md);
if (count > 0) {
limitCount.put(md, --count);
return getValue(md);
} else {
results.ofCount.add(md);
return 0;
}
}
return getValue(md);
if (limitCount.containsKey(md)) {
int count = limitCount.get(md);
if (count > 0) {
limitCount.put(md, --count);
return getValue(md);
} else {
results.ofCount.add(md);
return 0;
}
}
return getValue(md);
}
/**
* Scan all containers in a chunk and count their blocks
*
* @param chunk - the chunk to scan
*/
private void scanChests(Chunk chunk) {
@ -438,11 +357,11 @@ public class IslandLevelCalculator {
for (BlockState bs : chunk.getTileEntities()) {
if (bs instanceof Container container) {
if (addon.isAdvChestEnabled()) {
AdvancedChest<?,?> aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation());
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);
countItemStack((ItemStack) i);
}
});
continue;
@ -455,7 +374,8 @@ public class IslandLevelCalculator {
}
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()
@ -469,10 +389,13 @@ public class IslandLevelCalculator {
}
/**
* Scan the chunk chests and count the blocks. Note that the chunks are a list of all the island chunks
* in a particular world, so the memory usage is high, but I think most servers can handle it.
* Scan the chunk chests and count the blocks. Note that the chunks are a list
* of all the island chunks in a particular world, so the memory usage is high,
* but I think most servers can handle it.
*
* @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
* @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) {
// If the chunk hasn't been generated, return
@ -482,76 +405,74 @@ public class IslandLevelCalculator {
// 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.
* 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();
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));
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> result.complete(true));
});
return result;
}
record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {}
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) {
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) {
// 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(blockData.getMaterial())) {
Slab slab = (Slab)blockData;
if (Tag.SLABS.isTagged(m)) {
Slab slab = (Slab) blockData;
if (slab.getType().equals(Slab.Type.DOUBLE)) {
checkBlock(blockData.getMaterial(), belowSeaLevel);
checkBlock(m, 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));
// 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()) {
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());
}
}
}
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(blockData.getMaterial())) {
if (addon.getSettings().isIncludeChests() && CHESTS.contains(m)) {
chestBlocks.add(cp.chunk);
}
// Add the value of the block's material
checkBlock(blockData.getMaterial(), belowSeaLevel);
checkBlock(m, belowSeaLevel);
}
}
}
@ -559,7 +480,9 @@ public class IslandLevelCalculator {
/**
* 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() {
if (chunksToCheck.isEmpty()) {
@ -579,31 +502,28 @@ public class IslandLevelCalculator {
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()))))
)
)
);
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;
}
private Collection<String> sortedReport(int total, Multiset<Material> materialCount) {
Collection<String> result = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount).entrySet();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount)
.entrySet();
for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement();
int value = getValue(type);
result.add(type.toString() + ":"
+ String.format("%,d", en.getCount()) + " blocks x " + value + " = " + (value * en.getCount()));
result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = "
+ (value * en.getCount()));
total += (value * en.getCount());
}
@ -612,34 +532,29 @@ public class IslandLevelCalculator {
return result;
}
/**
* Finalizes the calculations and makes the report
*/
public void tidyUp() {
// Finalize calculations
results.rawBlockCount.addAndGet((long)(results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
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())
{
if (this.addon.getSettings().isSumTeamDeaths()) {
for (UUID uuid : this.island.getMemberSet()) {
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.
this.results.deathHandicap.set(this.island.getOwner() == null ? 0 :
this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner()));
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);
if (this.addon.getSettings().getDeathPenalty() > 0)
{
if (this.addon.getSettings().getDeathPenalty() > 0) {
// Proper death penalty calculation.
blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
}
@ -664,7 +579,7 @@ public class IslandLevelCalculator {
* @return the zeroIsland
*/
boolean isNotZeroIsland() {
return !zeroIsland;
return !zeroIsland;
}
public void scanIsland(Pipeliner pipeliner) {
@ -674,11 +589,13 @@ public class IslandLevelCalculator {
addon.getPlugin().logError("scanChunk not on Primary Thread!");
}
// Timeout check
if (System.currentTimeMillis() - pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) {
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());
addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout()
+ "m for island: " + getIsland());
if (!isNotZeroIsland()) {
addon.logError("Island level was being zeroed.");
}
@ -697,7 +614,8 @@ public class IslandLevelCalculator {
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) {
if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty())
|| System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
this.tidyUp();
this.getR().complete(getResults());
finishTask.cancel();
@ -710,7 +628,7 @@ public class IslandLevelCalculator {
private void handleChests() {
Iterator<Chunk> it = chestBlocks.iterator();
while(it.hasNext()) {
while (it.hasNext()) {
Chunk v = it.next();
Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> {
scanChests(c);
@ -720,27 +638,27 @@ public class IslandLevelCalculator {
}
private void handleStackedBlocks() {
// Deal with any stacked blocks
Iterator<Location> it = stackedBlocks.iterator();
while (it.hasNext()) {
Location v = it.next();
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);
}
}
it.remove();
});
}
// Deal with any stacked blocks
Iterator<Location> it = stackedBlocks.iterator();
while (it.hasNext()) {
Location v = it.next();
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);
}
}
it.remove();
});
}
}
}

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

@ -0,0 +1,98 @@
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.Map;
import java.util.UUID;
import java.util.Optional;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
@ -14,36 +14,32 @@ public class AdminTopCommand extends CompositeCommand {
private final Level levelPlugin;
public AdminTopCommand(Level addon, CompositeCommand parent) {
super(parent, "top", "topten");
this.levelPlugin = addon;
new AdminTopRemoveCommand(addon, this);
super(parent, "top", "topten");
this.levelPlugin = addon;
new AdminTopRemoveCommand(addon, this);
}
@Override
public void setup() {
this.setPermission("admin.top");
this.setOnlyPlayer(false);
this.setDescription("admin.top.description");
this.setPermission("admin.top");
this.setOnlyPlayer(false);
this.setDescription("admin.top.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
user.sendMessage("island.top.gui-title");
int rank = 0;
for (Map.Entry<UUID, Long> topTen : levelPlugin.getManager().getTopTen(getWorld(), Level.TEN).entrySet()) {
Island island = getPlugin().getIslands().getIsland(getWorld(), topTen.getKey());
if (island != null) {
rank++;
user.sendMessage("admin.top.display",
"[rank]",
String.valueOf(rank),
"[name]",
this.getPlugin().getPlayers().getUser(island.getOwner()).getName(),
"[level]",
String.valueOf(topTen.getValue()));
}
}
return true;
user.sendMessage("island.top.gui-title");
int rank = 0;
for (Map.Entry<String, Long> topTen : levelPlugin.getManager().getTopTen(getWorld(), Level.TEN).entrySet()) {
Optional<Island> is = getPlugin().getIslands().getIslandById(topTen.getKey());
if (is.isPresent()) {
Island island = is.get();
rank++;
user.sendMessage("admin.top.display", "[rank]", String.valueOf(rank), "[name]",
this.getPlugin().getPlayers().getUser(island.getOwner()).getName(), "[level]",
String.valueOf(topTen.getValue()));
}
}
return true;
}
}

View File

@ -6,10 +6,12 @@ import java.util.Optional;
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.level.Level;
/**
* Removes a player from the top ten
*
* @author tastybento
*
*/
@ -19,46 +21,53 @@ public class AdminTopRemoveCommand extends CompositeCommand {
private User target;
public AdminTopRemoveCommand(Level addon, CompositeCommand parent) {
super(parent, "remove", "delete");
this.addon = addon;
super(parent, "remove", "delete");
this.addon = addon;
}
@Override
public void setup() {
this.setPermission("admin.top.remove");
this.setOnlyPlayer(false);
this.setParametersHelp("admin.top.remove.parameters");
this.setDescription("admin.top.remove.description");
this.setPermission("admin.top.remove");
this.setOnlyPlayer(false);
this.setParametersHelp("admin.top.remove.parameters");
this.setDescription("admin.top.remove.description");
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.api.commands.BentoBoxCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)
/*
* (non-Javadoc)
*
* @see world.bentobox.bentobox.api.commands.BentoBoxCommand#canExecute(world.
* bentobox.bentobox.api.user.User, java.lang.String, java.util.List)
*/
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() != 1) {
this.showHelp(this, user);
return false;
}
target = getPlayers().getUser(args.get(0));
if (target == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (args.size() != 1) {
this.showHelp(this, user);
return false;
}
target = getPlayers().getUser(args.get(0));
if (target == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
return true;
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
addon.getManager().removeEntry(getWorld(), target.getUniqueId());
user.sendMessage("general.success");
return true;
// Removes islands that this target is an owner of
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");
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
return Optional.of(addon.getManager().getTopTen(getWorld(), Level.TEN).keySet().stream().map(addon.getPlayers()::getName)
.filter(n -> !n.isEmpty()).toList());
return Optional.of(addon.getManager().getTopTen(getWorld(), Level.TEN).keySet().stream()
.map(getIslands()::getIslandById).flatMap(Optional::stream).map(Island::getOwner)
.map(addon.getPlayers()::getName).filter(n -> !n.isEmpty()).toList());
}
}

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

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

View File

@ -1,5 +1,6 @@
package world.bentobox.level.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -126,6 +127,12 @@ public class ConfigSettings implements ConfigObject {
@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<>();
/**
* @return the gameModes
@ -404,4 +411,12 @@ public class ConfigSettings implements ConfigObject {
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

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

View File

@ -3,56 +3,41 @@ package world.bentobox.level.objects;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.bukkit.World;
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.
*
* @author tastybento
*
*/
@Table(name = "TopTenData")
public class TopTenData implements DataObject {
public class TopTenData {
// UniqueId is the world name
@Expose
private String uniqueId = "";
@Expose
private Map<UUID, Long> topTen = new LinkedHashMap<>();
private Map<String, Long> topTen = new LinkedHashMap<>();
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
*/
public Map<UUID, Long> getTopTen() {
return topTen;
public Map<String, Long> getTopTen() {
return topTen;
}
/**
* @param topTen the topTen to set
*/
public void setTopTen(Map<UUID, Long> topTen) {
this.topTen = topTen;
public void setTopTen(Map<String, Long> topTen) {
this.topTen = topTen;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,11 @@
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;
@ -29,474 +30,390 @@ 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
{
public class TopLevelPanel {
// ---------------------------------------------------------------------
// Section: Internal Constructor
// ---------------------------------------------------------------------
/**
* This is internal constructor. It is used internally in current class to avoid creating objects everywhere.
* 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 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;
private TopLevelPanel(Level addon, User user, World world, String permissionPrefix) {
this.addon = addon;
this.user = user;
this.world = world;
this.iconPermission = permissionPrefix + "level.icon";
this.topIslands = this.addon.getManager().getTopTen(this.world, 10).entrySet().stream().
map(entry -> {
Island island = this.addon.getIslandsManager().getIsland(this.world, entry.getKey());
return new IslandTopRecord(island, entry.getValue());
}).
collect(Collectors.toList());
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.
* 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();
public void build() {
TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
panelBuilder.user(this.user);
panelBuilder.world(this.world);
panelBuilder.user(this.user);
panelBuilder.world(this.world);
panelBuilder.template("top_panel", new File(this.addon.getDataFolder(), "panels"));
panelBuilder.template("top_panel", new File(this.addon.getDataFolder(), "panels"));
panelBuilder.registerTypeBuilder("VIEW", this::createViewerButton);
panelBuilder.registerTypeBuilder("TOP", this::createPlayerButton);
panelBuilder.registerTypeBuilder("VIEW", this::createViewerButton);
panelBuilder.registerTypeBuilder("TOP", this::createPlayerButton);
// Register unknown type builder.
panelBuilder.build();
// 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.
* @param index Place of the fallback.
* @return Fallback panel item.
*/
private PanelItem createFallback(ItemTemplateRecord template, long index)
{
if (template == null)
{
return null;
}
private PanelItem createFallback(ItemTemplateRecord template, long index) {
if (template == null) {
return null;
}
PanelItemBuilder builder = new PanelItemBuilder();
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
builder.icon(template.icon().clone());
}
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.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)));
}
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);
builder.amount(index != 0 ? (int) index : 1);
return builder.build();
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);
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);
}
if (index < 1) {
return this.createFallback(template.fallback(), index);
}
IslandTopRecord islandTopRecord = this.topIslands.size() < index ? null : this.topIslands.get(index - 1);
IslandTopRecord islandTopRecord = this.topIslands.size() < index ? null : this.topIslands.get(index - 1);
if (islandTopRecord == null)
{
return this.createFallback(template.fallback(), index);
}
if (islandTopRecord == null) {
return this.createFallback(template.fallback(), index);
}
return this.createIslandIcon(template, islandTopRecord, index);
return this.createIslandIcon(template, islandTopRecord, index);
}
/**
* This method creates button from template for given island top record.
* @param template Icon Template.
*
* @param template Icon Template.
* @param islandTopRecord Island Top Record.
* @param index Place Index.
* @param index Place Index.
* @return PanelItem for PanelBuilder.
*/
private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord islandTopRecord, int index)
{
// Get player island.
Island island = islandTopRecord.island();
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);
}
if (island == null) {
return this.createFallback(template.fallback(), index);
}
PanelItemBuilder builder = new PanelItemBuilder();
PanelItemBuilder builder = new PanelItemBuilder();
this.populateIslandIcon(builder, template, island);
this.populateIslandTitle(builder, template, island);
this.populateIslandDescription(builder, template, island, islandTopRecord, index);
this.populateIslandIcon(builder, template, island);
this.populateIslandTitle(builder, template, island);
this.populateIslandDescription(builder, template, island, islandTopRecord, index);
builder.amount(index);
builder.amount(index);
// Get only possible actions, by removing all inactive ones.
List<ItemTemplateRecord.ActionRecords> activeActions = new ArrayList<>(template.actions());
// 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);
}
case "VIEW" -> {
return island.getOwner() == null ||
!island.getMemberSet(RanksManager.MEMBER_RANK).contains(this.user.getUniqueId());
}
default -> {
return false;
}
}
});
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?
// 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();
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());
}
});
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());
}
}
}
}
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;
});
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())));
// 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);
}
// Add tooltips.
if (!tooltips.isEmpty()) {
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
return builder.build();
return builder.build();
}
/**
* Populate given panel item builder name with values from template and island objects.
* Populate given panel item builder name with values from template and island
* objects.
*
* @param builder the builder
* @param builder the builder
* @param template the template
* @param island the island
* @param island the island
*/
private void populateIslandTitle(PanelItemBuilder builder,
ItemTemplateRecord template,
Island island)
{
private void populateIslandTitle(PanelItemBuilder builder, ItemTemplateRecord template, Island island) {
// Get Island Name
String nameText;
// 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();
}
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));
}
// 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.
* Populate given panel item builder icon with values from template and island
* objects.
*
* @param builder the builder
* @param builder the builder
* @param template the template
* @param island the island
* @param island the island
*/
private void populateIslandIcon(PanelItemBuilder builder,
ItemTemplateRecord template,
Island island)
{
User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner());
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);
// Get permission or island icon
String permissionIcon = owner == null ? null : Utils.getPermissionValue(owner, this.iconPermission, null);
Material material;
Material material;
if (permissionIcon != null && !permissionIcon.equals("*"))
{
material = Material.matchMaterial(permissionIcon);
}
else
{
material = null;
}
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);
}
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.
* 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 builder the builder
* @param template the template
* @param island the island
* @param islandTopRecord the top record object
* @param index place index.
* @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()));
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;
// Get Members Text
String memberText;
if (island.getMemberSet().size() > 1)
{
StringBuilder memberBuilder = new StringBuilder(
this.user.getTranslationOrNothing(REFERENCE + "members-title"));
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);
for (UUID uuid : island.getMemberSet()) {
User member = User.getInstance(uuid);
if (memberBuilder.length() > 0)
{
memberBuilder.append("\n");
}
if (memberBuilder.length() > 0) {
memberBuilder.append("\n");
}
memberBuilder.append(
this.user.getTranslationOrNothing(REFERENCE + "member",
PLAYER, member.getName()));
}
memberBuilder.append(this.user.getTranslationOrNothing(REFERENCE + "member", PLAYER, member.getName()));
}
memberText = memberBuilder.toString();
}
else
{
memberText = "";
}
memberText = memberBuilder.toString();
} else {
memberText = "";
}
String placeText = this.user.getTranslation(REFERENCE + "place",
TextVariables.NUMBER, String.valueOf(index));
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()));
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);
// 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
}
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);
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;
}
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());
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);
IslandTopRecord topRecord = new IslandTopRecord(island, level);
return this.createIslandIcon(template, topRecord, place);
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.
* 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 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();
public static void openPanel(Level addon, User user, World world, String permissionPrefix) {
new TopLevelPanel(addon, user, world, permissionPrefix).build();
}
// ---------------------------------------------------------------------
@ -512,9 +429,12 @@ public class TopLevelPanel
/**
* 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) {}
private record IslandTopRecord(Island island, Long level) {
}
// ---------------------------------------------------------------------
// Section: Variables

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

@ -287,7 +287,7 @@ blocks:
GRANITE_SLAB: 1
GRANITE_STAIRS: 1
GRANITE_WALL: 1
GRASS: 4
SHORT_GRASS: 4
GRASS_BLOCK: 4
GRAVEL: 1
GRAY_BANNER: 2
@ -778,6 +778,58 @@ worlds:
ANDESITE: 0
DIORITE: 0
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
SANDSTONE: 0
RED_SAND: 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

@ -22,8 +22,18 @@ admin:
remove:
description: "remove player from Top Ten"
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:
level:
parameters: "[player]"
@ -32,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

@ -1,94 +1,79 @@
###########################################################################################################
# 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:
level:
parameters: "<player>"
description: "Calcula el nivel de la isla del jugador"
description: Calcula el nivel de la isla del jugador
sethandicap:
parameters: <player> <handicap>
description: "Define la desventaja de la isla, usualmente el nivel inicial para nuevas islas"
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"
description: Muestra cuantas islas hay en la cola para escanear
islands-in-queue: "&aIslas en cola: [number]"
top:
description: "Muestra la lista de las diez primeras islas"
description: Muestra la lista de las diez primeras islas
unknown-world: "&c¡Mundo desconocido!"
display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: "Elimina a un jugador de los diez primeros"
parameters: "<player>"
description: Elimina a un jugador de los diez primeros
parameters: "<jugador>"
island:
level:
level:
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..."
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]"
required-points-to-next-level: "&a[points] Puntos requeridos hasta el siguiente nivel."
required-points-to-next-level: "&a[points] Puntos requeridos hasta el siguiente
nivel."
deaths: "&c([number] Muertes)"
cooldown: "&cDebes esperar &b[time] &csegundos para poder volver a hacer esto."
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:
description: "Muestra el top de islas"
description: Muestra el top de islas
gui-title: "&aTop diez"
gui-heading: "&6[name]: &b[rank]"
island-level: "&bNivel [level]"
warp-to: "&aLlevándote 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"
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]"
names-island: Isla de [name]
syntax: "[name] x [number]"
hint: "&cEscriba /level para ver el recuento de bloques"
value:
description: "Muestra el valor de un bloque en la mano"
success: "&7El valor del este bloque es: &e[value]"
success-underwater: "&7El valor de este bloque debajo del nivel del mar es: &e[value]"
empty-hand: "&cNo hay bloques en tu mano."
no-value: "&cEste objeto no tiene valor."
level:
commands:
value:
parameters: "[hand|<material>]"
description: muestra el valor de los bloques. Añade 'hand' al final para mostrar
el valor del bloque de la mano.
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]'
empty: "&f&l[name]. lugar"
name: "&f&l[name]"
description: |-
[owner]
[members]
[place]
[level]
# Text that is replacing [name] if island do not have a name
owners-island: "Isla de [player]"
# Text for [owner] in description.
owners-island: Isla de [player]
owner: "&7&l Dueño: &r&b[player]"
# Title before listing members for [members] in description
members-title: "&7&l Miembros:"
# List each member under the title for [members] in description
member: "&b - [player]"
# Name of unknown player.
unknown: " desconocido"
# Section for parsing [place]
place: "&7&o [number]. &r&7lugar"
# Section for parsing [level]
level: "&7 Nivel: &o[number]"
material:
name: "&f&l[number] x [material]"
@ -123,31 +108,41 @@ level:
&7 nivel del mar.
spawner:
name: "&f&lSpawners"
description: |-
&7Mostrar solo spawners.
description: "&7Mostrar solo spawners."
filters:
name:
name: "&f&lOrdenar por nombre"
description: |-
&7Ordenar todos los bloques por nombre.
description: "&7Ordenar todos los bloques por nombre."
value:
name: "&f&lOrdenar por valor"
description: |-
&7Ordenar todos los bloques por valor.
description: "&7Ordenar todos los bloques por valor."
count:
name: "&f&lOrdenar por cantidad"
description: |-
&7Ordenar todos los bloques por cantidad.
# Button that is used in multi-page GUIs which allows to return to previous page.
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]
# Button that is used in multi-page GUIs which allows to go to next page.
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: |-
&7Cambiar a la página [number]
&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."
@ -155,7 +150,23 @@ level:
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 for messages that are send from server.
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: "&c No hay bloques en tu mano"

View File

@ -2,54 +2,170 @@
admin:
level:
parameters: "<player>"
description: hitung level pulau untuk player
description: hitung level pulau untuk pemain
sethandicap:
parameters: "<player> <handicap>"
description: mengatur handicap pulau, biasanya tingkat pulau pemula
changed: "& Handicap pulau awal diubah dari [number] menjadi [new_number]."
invalid-level: "& c Handicap tidak valid. Gunakan angka bulat."
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 pulau yang menunggu pindaian
islands-in-queue: "&a Pulau di dalam menunggu: [number]"
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 World tidak ditemukan!"
unknown-world: "&c Dunia tidak ditemukan!"
display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: menghilangkan player dari sepuluh besar
description: menghapus pemain dari sepuluh besar
parameters: "<player>"
island:
level:
parameters: "[player]"
description: hitung level pulau Anda atau tunjukkan level [player]
description: hitung level pulau kamu atau melihat level [player]
calculating: "&a Menghitung level..."
estimated-wait: "&a Waktu tunggu perkiraan: [number] detik"
in-queue: "&aAnda berada pada posisi [number] pada urutan menunggu"
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 Anda harus menunggu &b[time] &c detik sebelum Anda dapat melakukannya
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
description: menunjukkan Sepuluh Besar
gui-title: "&a Sepuluh Besar"
gui-heading: "&6[name]: &B[rank]"
island-level: "&b Level [level]"
warp-to: "&A Warping ke pulau milik [name]"
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 terdapat pulau!"
names-island: Pulau milik [name]
no-island: "&c Tidak ada pulau!"
names-island: Pulau [name]
syntax: "[name] x [number]"
hint: "& c Jalankan perintah level untuk melihat laporan blok"
value:
description: menunjukkan nilai dari apapun blok
success: "&7 Nilai blok ini adalah: &e[value]"
success-underwater: "&7 Nilai blok ini di bawah permukaan laut adalah: &e[value]"
empty-hand: "&c Tidak ada balok di tangan Anda"
no-value: "&c Benda itu tidak bernilai."
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

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

@ -5,7 +5,7 @@ admin:
description: 计算指定玩家的岛屿等级
sethandicap:
parameters: "<player> <handicap>"
description: 设置偏差值,通常用于调整新建的初始岛屿等级为零。实际岛屿等级 - <handicap> = 计算的岛屿等级
description: 设置偏差值,通常用于抵消初始岛屿等级,来保证岛屿等级从零开始。实际岛屿等级 - <handicap> = 最终的岛屿等级
changed: "&a 岛屿的偏差值从 [number] 更改为 [new_number]"
invalid-level: "&c 偏差值无效,请使用整数"
levelstatus:
@ -90,7 +90,7 @@ level:
name: "&f&l 所有方块"
description: "&7 显示岛屿上所有的方块"
above_sea_level:
name: "&f&l 方块在海平面以上的价值"
name: "&f&l 海平面以上的方块"
description: |-
&7 只显示所有
&7 海平面以上的方块
@ -122,7 +122,7 @@ level:
[id]
id: "&7 方块ID&e [id]"
value: "&7 方块价值:&e [number]"
underwater: "&7 方块海平面下价值:&e [number]"
underwater: "&7 海平面方块的价值:&e [number]"
limit: "&7 方块限制:&e [number]"
previous:
name: "&f&l 上一页"

View File

@ -1,25 +1,49 @@
# 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
title: "&b&r" # Empty text
# 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
@ -57,12 +81,12 @@ detail_panel:
9:
# You can create multiple buttons. By default it is one.
icon: IRON_TRAPDOOR
# [filter] is placeholder for different filter types. It will be replaced with name, value, count.
# [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 fist value to name if you use single button.
# the value of filter button. Suggestion is to leave first value to name if you use single button.
filter: NAME
actions:
up:
@ -76,6 +100,7 @@ detail_panel:
# 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
@ -85,7 +110,18 @@ detail_panel:
8: material_button
3:
1:
icon: TIPPED_ARROW:INSTANT_HEAL::::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:
@ -103,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:
@ -121,8 +157,12 @@ detail_panel:
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

View File

@ -1,15 +1,28 @@
# 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

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:

View File

@ -72,232 +72,229 @@ import world.bentobox.level.listeners.JoinLeaveListener;
*/
@SuppressWarnings("deprecation")
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, BentoBox.class, User.class})
@PrepareForTest({ Bukkit.class, BentoBox.class, User.class })
public class LevelTest {
private static File jFile;
@Mock
private User user;
@Mock
private IslandsManager im;
@Mock
private Island island;
@Mock
private BentoBox plugin;
@Mock
private FlagsManager fm;
@Mock
private GameModeAddon gameMode;
@Mock
private AddonsManager am;
@Mock
private BukkitScheduler scheduler;
private static File jFile;
@Mock
private User user;
@Mock
private IslandsManager im;
@Mock
private Island island;
@Mock
private BentoBox plugin;
@Mock
private FlagsManager fm;
@Mock
private GameModeAddon gameMode;
@Mock
private AddonsManager am;
@Mock
private BukkitScheduler scheduler;
@Mock
private Settings pluginSettings;
@Mock
private Settings pluginSettings;
private Level addon;
private Level addon;
@Mock
private Logger logger;
@Mock
private PlaceholdersManager phm;
@Mock
private CompositeCommand cmd;
@Mock
private CompositeCommand adminCmd;
@Mock
private World world;
private UUID uuid;
@Mock
private Logger logger;
@Mock
private PlaceholdersManager phm;
@Mock
private CompositeCommand cmd;
@Mock
private CompositeCommand adminCmd;
@Mock
private World world;
private UUID uuid;
@Mock
private PluginManager pim;
@Mock
private BlockConfig blockConfig;
@Mock
private PluginManager pim;
@Mock
private BlockConfig blockConfig;
@BeforeClass
public static void beforeClass() throws IOException {
// Make the addon jar
jFile = new File("addon.jar");
// Copy over config file from src folder
Path fromPath = Paths.get("src/main/resources/config.yml");
Path path = Paths.get("config.yml");
Files.copy(fromPath, path);
// Copy over block config file from src folder
fromPath = Paths.get("src/main/resources/blockconfig.yml");
path = Paths.get("blockconfig.yml");
Files.copy(fromPath, path);
try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) {
//Added the new files to the jar.
try (FileInputStream fis = new FileInputStream(path.toFile())) {
byte[] buffer = new byte[1024];
int bytesRead = 0;
JarEntry entry = new JarEntry(path.toString());
tempJarOutputStream.putNextEntry(entry);
while((bytesRead = fis.read(buffer)) != -1) {
tempJarOutputStream.write(buffer, 0, bytesRead);
}
}
}
}
@BeforeClass
public static void beforeClass() throws IOException {
// Make the addon jar
jFile = new File("addon.jar");
// Copy over config file from src folder
Path fromPath = Paths.get("src/main/resources/config.yml");
Path path = Paths.get("config.yml");
Files.copy(fromPath, path);
// Copy over block config file from src folder
fromPath = Paths.get("src/main/resources/blockconfig.yml");
path = Paths.get("blockconfig.yml");
Files.copy(fromPath, path);
try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) {
// Added the new files to the jar.
try (FileInputStream fis = new FileInputStream(path.toFile())) {
byte[] buffer = new byte[1024];
int bytesRead = 0;
JarEntry entry = new JarEntry(path.toString());
tempJarOutputStream.putNextEntry(entry);
while ((bytesRead = fis.read(buffer)) != -1) {
tempJarOutputStream.write(buffer, 0, bytesRead);
}
}
}
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger());
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger());
// The database type has to be created one line before the thenReturn() to work!
DatabaseType value = DatabaseType.JSON;
when(plugin.getSettings()).thenReturn(pluginSettings);
when(pluginSettings.getDatabaseType()).thenReturn(value);
// The database type has to be created one line before the thenReturn() to work!
DatabaseType value = DatabaseType.JSON;
when(plugin.getSettings()).thenReturn(pluginSettings);
when(pluginSettings.getDatabaseType()).thenReturn(value);
//when(plugin.isEnabled()).thenReturn(true);
// Command manager
CommandsManager cm = mock(CommandsManager.class);
when(plugin.getCommandsManager()).thenReturn(cm);
// when(plugin.isEnabled()).thenReturn(true);
// Command manager
CommandsManager cm = mock(CommandsManager.class);
when(plugin.getCommandsManager()).thenReturn(cm);
// Player
Player p = mock(Player.class);
// Sometimes use Mockito.withSettings().verboseLogging()
when(user.isOp()).thenReturn(false);
uuid = UUID.randomUUID();
when(user.getUniqueId()).thenReturn(uuid);
when(user.getPlayer()).thenReturn(p);
when(user.getName()).thenReturn("tastybento");
User.setPlugin(plugin);
// Player
Player p = mock(Player.class);
// Sometimes use Mockito.withSettings().verboseLogging()
when(user.isOp()).thenReturn(false);
uuid = UUID.randomUUID();
when(user.getUniqueId()).thenReturn(uuid);
when(user.getPlayer()).thenReturn(p);
when(user.getName()).thenReturn("tastybento");
User.setPlugin(plugin);
// Island World Manager
IslandWorldManager iwm = mock(IslandWorldManager.class);
when(plugin.getIWM()).thenReturn(iwm);
// Island World Manager
IslandWorldManager iwm = mock(IslandWorldManager.class);
when(plugin.getIWM()).thenReturn(iwm);
// Player has island to begin with
when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island);
when(plugin.getIslands()).thenReturn(im);
// Locales
// Return the reference (USE THIS IN THE FUTURE)
when(user.getTranslation(Mockito.anyString()))
.thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
// Player has island to begin with
when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island);
when(plugin.getIslands()).thenReturn(im);
// Server
PowerMockito.mockStatic(Bukkit.class);
Server server = mock(Server.class);
when(Bukkit.getServer()).thenReturn(server);
when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class));
// Locales
// Return the reference (USE THIS IN THE FUTURE)
when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
// Addon
addon = new Level();
File dataFolder = new File("addons/Level");
addon.setDataFolder(dataFolder);
addon.setFile(jFile);
AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test")
.authors("tastybento").build();
addon.setDescription(desc);
addon.setSettings(new ConfigSettings());
// Addons manager
when(plugin.getAddonsManager()).thenReturn(am);
// One game mode
when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode));
AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test")
.authors("tasty").build();
when(gameMode.getDescription()).thenReturn(desc2);
when(gameMode.getOverWorld()).thenReturn(world);
// Server
PowerMockito.mockStatic(Bukkit.class);
Server server = mock(Server.class);
when(Bukkit.getServer()).thenReturn(server);
when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class));
// Player command
@NonNull
Optional<CompositeCommand> opCmd = Optional.of(cmd);
when(gameMode.getPlayerCommand()).thenReturn(opCmd);
// Admin command
Optional<CompositeCommand> opAdminCmd = Optional.of(adminCmd);
when(gameMode.getAdminCommand()).thenReturn(opAdminCmd);
// Addon
addon = new Level();
File dataFolder = new File("addons/Level");
addon.setDataFolder(dataFolder);
addon.setFile(jFile);
AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test").authors("tastybento").build();
addon.setDescription(desc);
addon.setSettings(new ConfigSettings());
// Addons manager
when(plugin.getAddonsManager()).thenReturn(am);
// One game mode
when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode));
AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build();
when(gameMode.getDescription()).thenReturn(desc2);
when(gameMode.getOverWorld()).thenReturn(world);
// Flags manager
when(plugin.getFlagsManager()).thenReturn(fm);
when(fm.getFlags()).thenReturn(Collections.emptyList());
// Player command
@NonNull
Optional<CompositeCommand> opCmd = Optional.of(cmd);
when(gameMode.getPlayerCommand()).thenReturn(opCmd);
// Admin command
Optional<CompositeCommand> opAdminCmd = Optional.of(adminCmd);
when(gameMode.getAdminCommand()).thenReturn(opAdminCmd);
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(scheduler);
ItemMeta meta = mock(ItemMeta.class);
ItemFactory itemFactory = mock(ItemFactory.class);
when(itemFactory.getItemMeta(any())).thenReturn(meta);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
UnsafeValues unsafe = mock(UnsafeValues.class);
when(unsafe.getDataVersion()).thenReturn(777);
when(Bukkit.getUnsafe()).thenReturn(unsafe);
when(Bukkit.getPluginManager()).thenReturn(pim);
// Flags manager
when(plugin.getFlagsManager()).thenReturn(fm);
when(fm.getFlags()).thenReturn(Collections.emptyList());
// placeholders
when(plugin.getPlaceholdersManager()).thenReturn(phm);
// World
when(world.getName()).thenReturn("bskyblock-world");
// Island
when(island.getWorld()).thenReturn(world);
when(island.getOwner()).thenReturn(uuid);
}
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(scheduler);
ItemMeta meta = mock(ItemMeta.class);
ItemFactory itemFactory = mock(ItemFactory.class);
when(itemFactory.getItemMeta(any())).thenReturn(meta);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
UnsafeValues unsafe = mock(UnsafeValues.class);
when(unsafe.getDataVersion()).thenReturn(777);
when(Bukkit.getUnsafe()).thenReturn(unsafe);
when(Bukkit.getPluginManager()).thenReturn(pim);
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
deleteAll(new File("database"));
}
// placeholders
when(plugin.getPlaceholdersManager()).thenReturn(phm);
@AfterClass
public static void cleanUp() throws Exception {
new File("addon.jar").delete();
new File("config.yml").delete();
new File("blockconfig.yml").delete();
deleteAll(new File("addons"));
}
// World
when(world.getName()).thenReturn("bskyblock-world");
// Island
when(island.getWorld()).thenReturn(world);
when(island.getOwner()).thenReturn(uuid);
}
private static void deleteAll(File file) throws IOException {
if (file.exists()) {
Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
deleteAll(new File("database"));
}
/**
* Test method for {@link world.bentobox.level.Level#onEnable()}.
*/
@Test
public void testOnEnable() {
addon.onEnable();
verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world");
verify(plugin).log("[Level] Level hooking into BSkyBlock");
verify(cmd, times(3)).getAddon(); // 3 commands
verify(adminCmd, times(5)).getAddon(); // Five commands
// Placeholders
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_points_to_next_level"), any());
for (int i = 1; i < 11; i++) {
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_name_" + i), any());
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_value_" + i), any());
}
// Commands
verify(am).registerListener(eq(addon), any(IslandActivitiesListeners.class));
verify(am).registerListener(eq(addon), any(JoinLeaveListener.class));
}
@AfterClass
public static void cleanUp() throws Exception {
new File("addon.jar").delete();
new File("config.yml").delete();
new File("blockconfig.yml").delete();
deleteAll(new File("addons"));
}
private static void deleteAll(File file) throws IOException {
if (file.exists()) {
Files.walk(file.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
}
/**
* Test method for {@link world.bentobox.level.Level#onEnable()}.
*/
@Test
public void testOnEnable() {
addon.onEnable();
verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world");
verify(plugin).log("[Level] Level hooking into BSkyBlock");
verify(cmd, times(3)).getAddon(); // 3 commands
verify(adminCmd, times(4)).getAddon(); // Four commands
// Placeholders
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_points_to_next_level"), any());
for (int i = 1; i < 11; i++) {
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_name_" + i), any());
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_value_" + i), any());
}
// Commands
verify(am).registerListener(eq(addon), any(IslandActivitiesListeners.class));
verify(am).registerListener(eq(addon), any(JoinLeaveListener.class));
}
/**
* Test method for {@link world.bentobox.level.Level#getSettings()}.
*/
@Test
public void testGetSettings() {
addon.onEnable();
ConfigSettings s = addon.getSettings();
assertEquals(100, s.getLevelCost());
}
/**
* Test method for {@link world.bentobox.level.Level#getSettings()}.
*/
@Test
public void testGetSettings() {
addon.onEnable();
ConfigSettings s = addon.getSettings();
assertEquals(100, s.getLevelCost());
}
}

View File

@ -70,7 +70,7 @@ import world.bentobox.level.objects.TopTenData;
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class})
@PrepareForTest({ Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class })
public class LevelsManagerTest {
@Mock
@ -82,7 +82,6 @@ public class LevelsManagerTest {
@Mock
private Settings pluginSettings;
// Class under test
private LevelsManager lm;
@Mock
@ -114,18 +113,17 @@ public class LevelsManagerTest {
@Mock
private BukkitScheduler scheduler;
@SuppressWarnings("unchecked")
@BeforeClass
public static void beforeClass() {
// This has to be done beforeClass otherwise the tests will interfere with each other
handler = mock(AbstractDatabaseHandler.class);
// Database
PowerMockito.mockStatic(DatabaseSetup.class);
DatabaseSetup dbSetup = mock(DatabaseSetup.class);
when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
when(dbSetup.getHandler(any())).thenReturn(handler);
// This has to be done beforeClass otherwise the tests will interfere with each
// other
handler = mock(AbstractDatabaseHandler.class);
// Database
PowerMockito.mockStatic(DatabaseSetup.class);
DatabaseSetup dbSetup = mock(DatabaseSetup.class);
when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
when(dbSetup.getHandler(any())).thenReturn(handler);
}
/**
@ -162,10 +160,9 @@ public class LevelsManagerTest {
when(island.getMemberSet()).thenReturn(iset);
when(island.getOwner()).thenReturn(uuid);
when(island.getWorld()).thenReturn(world);
when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString());
when(island.getUniqueId()).thenReturn(uuid.toString());
// Default to uuid's being island owners
when(im.isOwner(eq(world), any())).thenReturn(true);
when(im.getOwner(any(), any(UUID.class))).thenAnswer(in -> in.getArgument(1, UUID.class));
when(im.hasIsland(eq(world), any(UUID.class))).thenReturn(true);
when(im.getIsland(world, uuid)).thenReturn(island);
when(im.getIslandById(anyString())).thenReturn(Optional.of(island));
@ -240,89 +237,92 @@ public class LevelsManagerTest {
*/
@After
public void tearDown() throws Exception {
deleteAll(new File("database"));
User.clearUsers();
Mockito.framework().clearInlineMocks();
deleteAll(new File("database"));
User.clearUsers();
Mockito.framework().clearInlineMocks();
}
private static void deleteAll(File file) throws IOException {
if (file.exists()) {
Files.walk(file.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
if (file.exists()) {
Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}.
*/
@Test
public void testCalculateLevel() {
Results results = new Results();
results.setLevel(10000);
results.setInitialLevel(3);
lm.calculateLevel(uuid, island);
// Complete the pipelined completable future
cf.complete(results);
Results results = new Results();
results.setLevel(10000);
results.setInitialLevel(3);
lm.calculateLevel(uuid, island);
// Complete the pipelined completable future
cf.complete(results);
assertEquals(10000L, lm.getLevelsData(island).getLevel());
//Map<UUID, Long> tt = lm.getTopTen(world, 10);
//assertEquals(1, tt.size());
//assertTrue(tt.get(uuid) == 10000);
assertEquals(10000L, lm.getIslandMaxLevel(world, uuid));
assertEquals(10000L, lm.getLevelsData(island).getLevel());
// Map<UUID, Long> tt = lm.getTopTen(world, 10);
// assertEquals(1, tt.size());
// 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));
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 {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}.
*/
@Test
public void testGetInitialLevel() {
assertEquals(0,lm.getInitialLevel(island));
assertEquals(0, lm.getInitialLevel(island));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetIslandLevel() {
assertEquals(-5, lm.getIslandLevel(world, uuid));
assertEquals(-5, lm.getIslandLevel(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetPointsToNextString() {
// No island player
assertEquals("", lm.getPointsToNextString(world, UUID.randomUUID()));
// Player has island
assertEquals("0", lm.getPointsToNextString(world, uuid));
// No island player
assertEquals("", lm.getPointsToNextString(world, UUID.randomUUID()));
// Player has island
assertEquals("0", lm.getPointsToNextString(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetIslandLevelString() {
assertEquals("-5", lm.getIslandLevelString(world, uuid));
assertEquals("-5", lm.getIslandLevelString(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}.
*/
@Test
public void testGetLevelsData() {
assertEquals(levelsData, lm.getLevelsData(island));
assertEquals(levelsData, lm.getLevelsData(island));
}
@ -331,54 +331,59 @@ public class LevelsManagerTest {
*/
@Test
public void testFormatLevel() {
assertEquals("123456789", lm.formatLevel(123456789L));
when(settings.isShorthand()).thenReturn(true);
assertEquals("123.5M", lm.formatLevel(123456789L));
assertEquals("1.2k", lm.formatLevel(1234L));
assertEquals("123.5G", lm.formatLevel(123456789352L));
assertEquals("1.2T", lm.formatLevel(1234567893524L));
assertEquals("12345.7T", lm.formatLevel(12345678345345349L));
assertEquals("123456789", lm.formatLevel(123456789L));
when(settings.isShorthand()).thenReturn(true);
assertEquals("123.5M", lm.formatLevel(123456789L));
assertEquals("1.2k", lm.formatLevel(1234L));
assertEquals("123.5G", lm.formatLevel(123456789352L));
assertEquals("1.2T", lm.formatLevel(1234567893524L));
assertEquals("12345.7T", lm.formatLevel(12345678345345349L));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
*/
@Test
public void testGetTopTenEmpty() {
Map<UUID, Long> tt = lm.getTopTen(world, Level.TEN);
assertTrue(tt.isEmpty());
Map<String, Long> tt = lm.getTopTen(world, Level.TEN);
assertTrue(tt.isEmpty());
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
*/
@Test
public void testGetTopTen() {
testLoadTopTens();
Map<UUID, Long> tt = lm.getTopTen(world, Level.TEN);
assertFalse(tt.isEmpty());
assertEquals(1, tt.size());
assertEquals(1, lm.getTopTen(world, 1).size());
testLoadTopTens();
Map<String, Long> tt = lm.getTopTen(world, Level.TEN);
assertFalse(tt.isEmpty());
assertEquals(1, tt.size());
assertEquals(1, lm.getTopTen(world, 1).size());
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#getWeightedTopTen(org.bukkit.World, int)}.
*/
@Test
public void testGetTopTenNoOwners() {
when(im.isOwner(eq(world), any())).thenReturn(false);
testLoadTopTens();
Map<UUID, Long> tt = lm.getTopTen(world, Level.TEN);
assertTrue(tt.isEmpty());
public void testGetWeightedTopTen() {
testLoadTopTens();
Map<Island, Long> tt = lm.getWeightedTopTen(world, Level.TEN);
assertFalse(tt.isEmpty());
assertEquals(1, tt.size());
assertEquals(1, lm.getTopTen(world, 1).size());
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testHasTopTenPerm() {
assertTrue(lm.hasTopTenPerm(world, uuid));
assertTrue(lm.hasTopTenPerm(world, uuid));
}
/**
@ -386,69 +391,72 @@ public class LevelsManagerTest {
*/
@Test
public void testLoadTopTens() {
ArgumentCaptor<Runnable> task = ArgumentCaptor.forClass(Runnable.class);
lm.loadTopTens();
PowerMockito.verifyStatic(Bukkit.class); // 1
Bukkit.getScheduler();
verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture());
task.getValue().run();
verify(addon).log("Generating rankings");
verify(addon).log("Generated rankings for bskyblock-world");
ArgumentCaptor<Runnable> task = ArgumentCaptor.forClass(Runnable.class);
lm.loadTopTens();
PowerMockito.verifyStatic(Bukkit.class); // 1
Bukkit.getScheduler();
verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture());
task.getValue().run();
verify(addon).log("Generating rankings");
verify(addon).log("Generated rankings for bskyblock-world");
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testRemoveEntry() {
testLoadTopTens();
Map<UUID, Long> tt = lm.getTopTen(world, Level.TEN);
assertTrue(tt.containsKey(uuid));
lm.removeEntry(world, uuid);
tt = lm.getTopTen(world, Level.TEN);
assertFalse(tt.containsKey(uuid));
testLoadTopTens();
Map<String, Long> tt = lm.getTopTen(world, Level.TEN);
assertTrue(tt.containsKey(uuid.toString()));
lm.removeEntry(world, uuid.toString());
tt = lm.getTopTen(world, Level.TEN);
assertFalse(tt.containsKey(uuid.toString()));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}.
*/
@Test
public void testSetInitialIslandLevel() {
lm.setInitialIslandLevel(island, Level.TEN);
assertEquals(Level.TEN, lm.getInitialLevel(island));
lm.setInitialIslandLevel(island, Level.TEN);
assertEquals(Level.TEN, lm.getInitialLevel(island));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}.
* Test method for
* {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}.
*/
@Test
public void testSetIslandLevel() {
lm.setIslandLevel(world, uuid, 1234);
assertEquals(1234, lm.getIslandLevel(world, uuid));
lm.setIslandLevel(world, uuid, 1234);
assertEquals(1234, lm.getIslandLevel(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getRank(World, UUID)}
* Test method for
* {@link world.bentobox.level.LevelsManager#getRank(World, UUID)}
*/
@Test
public void testGetRank() {
lm.createAndCleanRankings(world);
Map<World, TopTenData> ttl = lm.getTopTenLists();
Map<UUID, Long> tt = ttl.get(world).getTopTen();
for (long i = 100; i < 150; i++) {
tt.put(UUID.randomUUID(), i);
}
// Put player as lowest rank
tt.put(uuid, 10L);
assertEquals(51, lm.getRank(world, uuid));
// Put player as highest rank
tt.put(uuid, 1000L);
assertEquals(1, lm.getRank(world, uuid));
// Unknown UUID - lowest rank + 1
assertEquals(52, lm.getRank(world, UUID.randomUUID()));
lm.createAndCleanRankings(world);
Map<World, TopTenData> ttl = lm.getTopTenLists();
Map<String, Long> tt = ttl.get(world).getTopTen();
for (long i = 100; i < 150; i++) {
tt.put(UUID.randomUUID().toString(), i);
}
// 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

@ -3,6 +3,7 @@ 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;
@ -10,9 +11,10 @@ import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@ -44,7 +46,7 @@ import world.bentobox.level.objects.IslandLevels;
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({BentoBox.class})
@PrepareForTest({ BentoBox.class })
public class PlaceholderManagerTest {
@Mock
@ -54,7 +56,7 @@ public class PlaceholderManagerTest {
@Mock
private BentoBox plugin;
private PlaceholderManager pm;
private PlaceholderManager phm;
@Mock
private PlaceholdersManager bpm;
@Mock
@ -67,13 +69,25 @@ public class PlaceholderManagerTest {
private Island island;
@Mock
private User user;
private Map<UUID, String> names = new HashMap<>();
private static final List<String> NAMES = List.of("tasty", "bento", "fred", "bonne", "cyprien", "mael", "joe", "horacio", "steph", "vicky");
private Map<UUID, Island> islands = new HashMap<>();
private Map<UUID, Long> map = new HashMap<>();
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 players;
private PlayersManager pm;
/**
* @throws java.lang.Exception
@ -83,29 +97,32 @@ public class PlaceholderManagerTest {
when(addon.getPlugin()).thenReturn(plugin);
// Users
when(addon.getPlayers()).thenReturn(players);
when(addon.getPlayers()).thenReturn(pm);
// Users
when(user.getWorld()).thenReturn(world);
when(user.getLocation()).thenReturn(mock(Location.class));
for (int i = 0; i < Level.TEN; i++) {
UUID uuid = UUID.randomUUID();
names.put(uuid, NAMES.get(i));
map.put(uuid, (long)(100 - i));
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.setOwner(uuid);
is.setName(NAMES.get(i) + "'s island");
islands.put(uuid, is);
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(players.getName(any())).thenAnswer((Answer<String>) invocation -> names.getOrDefault(invocation.getArgument(0, UUID.class), "unknown"));
when(pm.getName(any())).thenAnswer((Answer<String>) invocation -> names.getOrDefault(invocation.getArgument(0, UUID.class), "unknown"));
Map<UUID, Integer> members = new HashMap<>();
map.forEach((uuid, l) -> members.put(uuid, RanksManager.MEMBER_RANK));
islands.values().forEach(i -> i.setMembers(members));
names.forEach((uuid, l) -> members.put(uuid, RanksManager.MEMBER_RANK));
islands.values().forEach(is -> is.setMembers(members));
// Placeholders manager for plugin
@ -120,7 +137,8 @@ public class PlaceholderManagerTest {
// Islands
when(im.getIsland(any(World.class), any(User.class))).thenReturn(island);
when(im.getIslandAt(any(Location.class))).thenReturn(Optional.of(island));
when(im.getIsland(any(World.class), any(UUID.class))).thenAnswer((Answer<Island>) invocation -> islands.get(invocation.getArgument(1, UUID.class)));
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 HashSet<>(islands.values()));
when(addon.getIslands()).thenReturn(im);
// Levels Manager
@ -129,6 +147,7 @@ public class PlaceholderManagerTest {
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");
@ -136,127 +155,150 @@ public class PlaceholderManagerTest {
when(lm.getLevelsData(island)).thenReturn(data);
when(addon.getManager()).thenReturn(lm);
pm = new PlaceholderManager(addon);
phm = new PlaceholderManager(addon);
}
/**
* Test method for {@link world.bentobox.level.PlaceholderManager#PlaceholderManager(world.bentobox.level.Level)}.
* Test method for
* {@link world.bentobox.level.PlaceholderManager#PlaceholderManager(world.bentobox.level.Level)}.
*/
@Test
public void testPlaceholderManager() {
verify(addon).getPlugin();
verify(addon).getPlugin();
}
/**
* Test method for {@link world.bentobox.level.PlaceholderManager#registerPlaceholders(world.bentobox.bentobox.api.addons.GameModeAddon)}.
* Test method for
* {@link world.bentobox.level.PlaceholderManager#registerPlaceholders(world.bentobox.bentobox.api.addons.GameModeAddon)}.
*/
@Test
public void testRegisterPlaceholders() {
pm.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());
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());
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());
// 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());
}
// 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());
// 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 method for
* {@link world.bentobox.level.PlaceholderManager#getRankName(org.bukkit.World, int)}.
*/
@Test
public void testGetRankName() {
// Test extremes
assertEquals("tasty", pm.getRankName(world, 0));
assertEquals("vicky", pm.getRankName(world, 100));
// Test the ranks
int rank = 1;
for (String name : NAMES) {
assertEquals(name, pm.getRankName(world, rank++));
}
// 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 method for
* {@link world.bentobox.level.PlaceholderManager#getRankIslandName(org.bukkit.World, int)}.
*/
@Test
public void testGetRankIslandName() {
// Test extremes
assertEquals("tasty's island", pm.getRankIslandName(world, 0));
assertEquals("vicky's island", pm.getRankIslandName(world, 100));
// Test the ranks
int rank = 1;
for (String name : NAMES) {
assertEquals(name + "'s island", pm.getRankIslandName(world, rank++));
}
// 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 method for
* {@link world.bentobox.level.PlaceholderManager#getRankMembers(org.bukkit.World, int)}.
*/
@Test
public void testGetRankMembers() {
// Test extremes
check(1, pm.getRankMembers(world, 0));
check(2, pm.getRankMembers(world, 100));
// Test the ranks
for (int rank = 1; rank < 11; rank++) {
check(3, pm.getRankMembers(world, rank));
}
// 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) {
assertTrue(n + " is missing for twst " + indicator, list.contains(n));
}
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 method for
* {@link world.bentobox.level.PlaceholderManager#getRankLevel(org.bukkit.World, int)}.
*/
@Test
public void testGetRankLevel() {
// Test extremes
assertEquals("100", pm.getRankLevel(world, 0));
assertEquals("91", pm.getRankLevel(world, 100));
// Test the ranks
for (int rank = 1; rank < 11; rank++) {
assertEquals(String.valueOf(101 - rank), pm.getRankLevel(world, rank));
}
// 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#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}.
* 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("", pm.getVisitedIslandLevel(gm, null));
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)}.
*/
@ -264,26 +306,27 @@ public class PlaceholderManagerTest {
public void testGetVisitedIslandLevelUserNotInWorld() {
// Another world
when(user.getWorld()).thenReturn(mock(World.class));
assertEquals("", pm.getVisitedIslandLevel(gm, user));
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 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", pm.getVisitedIslandLevel(gm, user));
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", pm.getVisitedIslandLevel(gm, user));
assertEquals("0", phm.getVisitedIslandLevel(gm, user));
}

View File

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

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

@ -0,0 +1,208 @@
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.Set;
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(Set.of(island));
when(im.getIslands(any(), any(UUID.class))).thenReturn(Set.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

@ -1,199 +0,0 @@
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("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(any(World.class), eq(uuid));
verify(user).sendMessage("general.success");
}
}