Compare commits

...

53 Commits

Author SHA1 Message Date
tastybento f536a13c59 Back to 1.20.6 2024-05-12 17:20:14 -07:00
tastybento f4780659e3 Downgrade to 1.20.5 to get tests to pass.
Tech debt - need to reword tests for 1.20.6
2024-05-11 13:21:00 -07:00
tastybento aad50eab38 Just use null. 2024-05-11 13:03:50 -07:00
tastybento 24d81da907 Update to latest 1.20.6 API for PlayerDeathEvent 2024-05-11 12:55:14 -07:00
tastybento d288528a17
2356 better deletion (#2364)
* Fix 1.20.4 backwards compatibility

* Improve deletion speed and memory usage
2024-05-11 11:07:47 -07:00
tastybento 83698c267f
Purges based on team members all being offline for too long (#2362)
* Purges based on team members all being offline for too long

* Fix to riff off real team members not trusts and banned as well
2024-05-11 08:50:28 -07:00
tastybento 52a280dc0b
Remove an unused Map in cache. (#2361) 2024-05-11 08:50:03 -07:00
tastybento 4a0d44c035
Implement new API for ItemsAdder item deletion (#2353) 2024-05-11 08:49:47 -07:00
tastybento b8e1f33800
Fixes #2352 obsidian scooping NPE (#2358) 2024-05-07 21:25:58 -07:00
tastybento d8891796cd JavaDoc fixes and fix for Particle enums 2024-05-05 21:28:04 -07:00
tastybento 61e7c22bbc
Add a hook for Multipaper (#2354) 2024-05-05 21:11:16 -07:00
tastybento b1fe76c45d
Multipaper (#2343)
* Switch to use database for team invites.

* WIP multipaper

* Fixes teams. Test still need to be fixed.

* Islands are now updated correctly across servers.

This build has a lot of debug in it!

* Fix tests

* Remove debug

* Remove primary island listing

* Version id

* Fix team management and ranks

* Removed debug

* Handle island deletion better

* Island deletion across servers.

* Fix bug with MythicMobs changes #2340

* 2.4.0

* Load of debug - trying to solve the settings slowness

* Debug debug

* Bug found - addPlayer being called instead of getPlayer

* Uncomment code after debug

* Fix tests
2024-05-04 22:27:58 -07:00
tastybento 5afd454fb3 Update Slimefun 2024-05-04 21:19:21 -07:00
tastybento a55c51412d Shift Github Build Script to Java 21 2024-05-04 21:05:19 -07:00
tastybento 01a8055379
Merge pull request #2348 from BentoBoxWorld/1.20.5_compatibility
1.20.6 compatibility
2024-05-04 13:29:15 -07:00
tastybento 744665a16e
Merge branch 'develop' into 1.20.5_compatibility 2024-05-01 18:01:51 -07:00
tastybento 3e4ff33d30
Merge pull request #2349 from BentoBoxWorld/Blueprint_Bundle_Info
Write the Blueprint bundle meta data to admin info
2024-05-01 17:58:34 -07:00
tastybento 09ede87971 Write the Blueprint bundle meta data to admin info 2024-05-01 17:58:05 -07:00
tastybento b1418c144f
Update README.md 2024-05-01 08:05:11 -07:00
tastybento 15335eb992 Add NMS for latest 2024-04-29 22:26:09 -07:00
tastybento e33823d0c0 Fix tests 2024-04-29 21:37:08 -07:00
tastybento 63cc0a01d9 Fix compatibility for 1.20.6 2024-04-29 19:46:06 -07:00
tastybento 6949432cb6 1.20.5 compatibility. Not all backward compatibility done
Won't run on 1.20.4 yet without stopping due to enum incompatibility
2024-04-28 17:56:02 -07:00
tastybento 69a22e917e Fix MythicMobs test 2024-04-14 22:11:41 -07:00
tastybento ffb955b22b Fix bug with MythicMobs changes #2340 2024-04-14 22:00:37 -07:00
tastybento 3de0ff236e Fix tests 2024-04-08 23:01:45 -07:00
tastybento c86eb6a19c Make sure it's the user's island that the target is being kicked from 2024-04-08 22:11:52 -07:00
tastybento e7055c6cba Remove player from island, not all islands when kicked. 2024-04-08 21:53:18 -07:00
tastybento 5834dcbb59 Fix placeholders manager test 2024-04-08 20:15:34 -07:00
tastybento 2c75939bc3 JavaDoc and import changes 2024-04-06 09:08:11 -07:00
tastybento e570401912
Merge pull request #2337 from BentoBoxWorld/2329_placeholders_for_islands
Added placeholders for island names and member lists #2329
2024-04-04 16:30:20 -07:00
tastybento f6f4da1c89 Added placeholders for island names and member lists #2329 2024-04-04 12:59:20 -07:00
tastybento 6106b661e9
Merge pull request #2335 from BentoBoxWorld/2328_team_members_can_have_islands
Remove restrictions on having multiple islands for team members.
2024-04-04 10:23:12 -07:00
tastybento e1536fcae0 Remove superfluous null check 2024-04-04 09:12:45 -07:00
tastybento 1c19703f44 Merge if statement 2024-04-04 09:11:49 -07:00
tastybento 24b7d26fbe NPE avoid. 2024-04-04 09:09:46 -07:00
tastybento ec60991aeb Merge branch 'develop' into 2328_team_members_can_have_islands 2024-04-01 10:59:30 -07:00
tastybento 33000f9371
Merge pull request #2336 from BentoBoxWorld/2333_bundle_limits
Add feature to limit blueprint availability.
2024-03-31 18:17:59 -07:00
tastybento 83eaa50b49 Refactor to improve code quality 2024-03-31 18:16:45 -07:00
tastybento 1215a43766 Add feature to limit blueprint availability. 2024-03-31 17:29:18 -07:00
tastybento 81f765df36 Fix invites and accepts. 2024-03-30 20:57:20 -07:00
tastybento 0e6a25d74b WIP - needs work on team invites. 2024-03-29 20:26:07 -07:00
tastybento 2b19d43c85 Remove restrictions on having multiple islands for team members.
Added API to enable checking for teams on islands easier.
2024-03-29 19:38:44 -07:00
tastybento 1bce4ec1b9 Limit blueprint pasting to world y coords. Addresses #2334 2024-03-28 21:58:01 -07:00
tastybento ea8562f351 Remove more debug 2024-03-28 21:42:33 -07:00
tastybento d8f2c12fe5 Remove pasting blueprints as stone debug. 2024-03-28 21:27:48 -07:00
tastybento b734d579a1
Merge pull request #2332 from BentoBoxWorld/admin_range_multi_island
Adds support for multi islands to the admin range command
2024-03-24 18:47:39 -07:00
tastybento 44454f5854 Adds support for multi islands to the admin range command 2024-03-24 18:44:24 -07:00
tastybento da590ce319
Merge pull request #2331 from BentoBoxWorld/sort_islands
Sort player's islands by age so they are always in the same order.
2024-03-21 19:30:42 -07:00
tastybento 6599e3de80 Sort player's islands by age so they are always in the same order. 2024-03-21 19:20:31 -07:00
tastybento 3c6e3d1286
Merge pull request #2327 from BentoBoxWorld/2320_hide_flags_in_other_world
Fix #2320. Enables hiding of flags when in another world
2024-03-16 10:26:27 -07:00
tastybento d77c94c30c Fix #2320. Enables hiding of flags when in another world
The world was being taken from the user's location not the panel's world
2024-03-16 10:04:28 -07:00
tastybento d240e9c8d8 Version 2.2.1 2024-03-16 09:15:46 -07:00
164 changed files with 2806 additions and 2112 deletions

View File

@ -13,11 +13,11 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '17'
java-version: '21'
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:

View File

@ -112,9 +112,10 @@ repositories {
}
dependencies {
compileOnly 'world.bentobox:bentobox:PUT-VERSION-HERE'
compileOnly 'world.bentobox:bentobox:PUT-VERSION-HERE-SNAPSHOT'
}
```
**Note:** Due to a Gradle issue with versions for Maven, you need to use -SNAPSHOT at the end.
### History

49
pom.xml
View File

@ -73,10 +73,10 @@
<postgresql.version>42.2.18</postgresql.version>
<hikaricp.version>5.0.1</hikaricp.version>
<!-- More visible way to change dependency versions -->
<spigot.version>1.20.4-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.20.5-R0.1-SNAPSHOT</spigot.version>
<!-- Might differ from the last Spigot release for short periods
of time -->
<paper.version>1.20.4-R0.1-SNAPSHOT</paper.version>
<paper.version>1.20.6-R0.1-SNAPSHOT</paper.version>
<bstats.version>3.0.0</bstats.version>
<vault.version>1.7.1</vault.version>
<placeholderapi.version>2.10.9</placeholderapi.version>
@ -88,7 +88,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.2.0</build.version>
<build.version>2.4.0</build.version>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<server.jars>${project.basedir}/lib</server.jars>
@ -145,6 +145,10 @@
</pluginRepositories>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
@ -153,10 +157,6 @@
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>placeholderapi-repo</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
@ -195,6 +195,11 @@
<name>Lumine Releases</name>
<url>https://mvn.lumine.io/repository/maven-public/</url>
</repository>
<!-- For Multipaper -->
<repository>
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
</repositories>
<dependencies>
@ -223,13 +228,19 @@
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<!-- Spigot API -->
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc....</groupId>
<artifactId>spigot</artifactId>
<version>1.20.6-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc.</groupId>
<artifactId>spigot</artifactId>
@ -354,16 +365,23 @@
<dependency>
<groupId>com.github.Slimefun</groupId>
<artifactId>Slimefun4</artifactId>
<version>RC-36</version>
<version>RC-37</version>
<scope>provided</scope>
</dependency>
<!-- ItemsAdder -->
<dependency>
<groupId>com.github.LoneDev6</groupId>
<artifactId>api-itemsadder</artifactId>
<version>3.6.1</version>
<version>3.6.3-beta-14</version>
<scope>provided</scope>
</dependency>
<!-- Multipaper -->
<dependency>
<groupId>com.github.puregero</groupId>
<artifactId>multilib</artifactId>
<version>1.1.13</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@ -486,9 +504,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.1-SNAPSHOT</version>
<version>3.4.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
@ -500,9 +519,13 @@
</relocation>
<relocation>
<pattern>io.papermc.lib</pattern>
<shadedPattern>world.bentobox.bentobox.paperlib</shadedPattern> <!-- Replace this -->
<shadedPattern>world.bentobox.bentobox.paperlib</shadedPattern>
</relocation>
</relocations>
<relocation>
<pattern>com.github.puregero.multilib</pattern>
<shadedPattern>world.bentobox.bentobox.multilib</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.apache.maven.shared:*</exclude>

View File

@ -59,7 +59,6 @@ public class BStats {
registerGameModeAddonsChart();
registerHooksChart();
registerPlayersPerServerChart();
registerFlagsDisplayModeChart();
// Single Line charts
registerIslandsCountChart();
@ -171,27 +170,6 @@ public class BStats {
}));
}
/**
* Sends the "flags display mode" of all the online players.
* @since 1.6.0
*/
private void registerFlagsDisplayModeChart() {
metrics.addCustomChart(new AdvancedPie("flagsDisplayMode", () -> {
Map<String, Integer> values = new HashMap<>();
Bukkit.getOnlinePlayers().forEach(player -> {
Flag.Mode mode = plugin.getPlayers().getFlagsDisplayMode(player.getUniqueId());
if (values.containsKey(mode.name())) {
values.put(mode.name(), values.get(mode.name()) + 1);
} else {
values.put(mode.name(), 1);
}
});
return values;
}));
}
/**
* Sends the enabled addons (except GameModeAddons) of this server as bar chart.
* @since 1.17.1

View File

@ -25,6 +25,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.MultipaperHook;
import world.bentobox.bentobox.hooks.MultiverseCoreHook;
import world.bentobox.bentobox.hooks.MyWorldsHook;
import world.bentobox.bentobox.hooks.MythicMobsHook;
@ -37,6 +38,7 @@ import world.bentobox.bentobox.listeners.DeathListener;
import world.bentobox.bentobox.listeners.JoinLeaveListener;
import world.bentobox.bentobox.listeners.PanelListenerManager;
import world.bentobox.bentobox.listeners.PrimaryIslandListener;
import world.bentobox.bentobox.listeners.SeedWorldMakerListener;
import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener;
import world.bentobox.bentobox.listeners.teleports.EntityTeleportListener;
import world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener;
@ -184,6 +186,9 @@ public class BentoBox extends JavaPlugin implements Listener {
private void completeSetup(long loadTime) {
final long enableStart = System.currentTimeMillis();
hooksManager.registerHook(new MultipaperHook());
hooksManager.registerHook(new VaultHook());
// MythicMobs
@ -210,20 +215,6 @@ public class BentoBox extends JavaPlugin implements Listener {
return;
}
// Save islands & players data every X minutes
Bukkit.getScheduler().runTaskTimer(instance, () -> {
if (!playersManager.isSaveTaskRunning()) {
playersManager.saveAll(true);
} else {
getLogger().warning("Tried to start a player data save task while the previous auto save was still running!");
}
if (!islandsManager.isSaveTaskRunning()) {
islandsManager.saveAll(true);
} else {
getLogger().warning("Tried to start a island data save task while the previous auto save was still running!");
}
}, getSettings().getDatabaseBackupPeriod() * 20 * 60L, getSettings().getDatabaseBackupPeriod() * 20 * 60L);
// Make sure all flag listeners are registered.
flagsManager.registerListeners();
@ -323,6 +314,8 @@ public class BentoBox extends JavaPlugin implements Listener {
manager.registerEvents(islandDeletionManager, this);
// Primary Island Listener
manager.registerEvents(new PrimaryIslandListener(this), this);
// Seed world chunk generator
manager.registerEvents(new SeedWorldMakerListener(this), this);
}
@Override
@ -433,7 +426,7 @@ public class BentoBox extends JavaPlugin implements Listener {
* @return the ranksManager
* @deprecated Just use {@code RanksManager.getInstance()}
*/
@Deprecated(since = "2.0.0")
@Deprecated(since = "2.0.0", forRemoval = true)
public RanksManager getRanksManager() {
return RanksManager.getInstance();
}

View File

@ -195,6 +195,12 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "island.concurrent-islands")
private int islandNumber = 1;
@ConfigComment("Hide used blueprints.")
@ConfigComment("Blueprints can have a maximum use when players have concurrent islands.")
@ConfigComment("If this is true, then ones that are used up will not be shown in the island create menu.")
@ConfigEntry(path = "island.hide-used-blueprints", since = "2.3.0")
private boolean hideUsedBlueprints = false;
// Cooldowns
@ConfigComment("How long a player must wait until they can rejoin a team island after being kicked in minutes.")
@ConfigComment("This slows the effectiveness of players repeating challenges")
@ -1014,4 +1020,18 @@ public class Settings implements ConfigObject {
this.islandNumber = islandNumber;
}
/**
* @return the hideUsedBlueprints
*/
public boolean isHideUsedBlueprints() {
return hideUsedBlueprints;
}
/**
* @param hideUsedBlueprints the hideUsedBlueprints to set
*/
public void setHideUsedBlueprints(boolean hideUsedBlueprints) {
this.hideUsedBlueprints = hideUsedBlueprints;
}
}

View File

@ -21,6 +21,8 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.Listener;
import com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.request.AddonRequestHandler;
import world.bentobox.bentobox.api.flags.Flag;
@ -45,6 +47,8 @@ public abstract class Addon {
protected Addon() {
state = State.DISABLED;
// If the config is updated, update the config.
MultiLib.onString(getPlugin(), "bentobox-config-update", v -> this.reloadConfig());
}
/**

View File

@ -8,6 +8,8 @@ import org.bukkit.generator.ChunkGenerator;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.util.Util;
@ -129,7 +131,10 @@ public abstract class GameModeAddon extends Addon {
* in-game and need to be saved.
* @since 1.4.0
*/
public abstract void saveWorldSettings();
public void saveWorldSettings() {
// Inform other servers
MultiLib.notify("bentobox-config-update", "");
}
/**
* Defines if the game mode uses the latest {@link ChunkGenerator} API or

View File

@ -7,6 +7,8 @@ import org.bukkit.plugin.java.JavaPlugin;
import com.google.common.io.Files;
import world.bentobox.bentobox.BentoBox;
/**
* Provides a shell for addons to become Plugins so that other Plugins
* can tap into their API more easily. Plugin + addon = Pladdon
@ -16,6 +18,8 @@ import com.google.common.io.Files;
public abstract class Pladdon extends JavaPlugin {
private static final String ADDONS_FOLDER = "BentoBox" + File.separator + "addons";
private static final String PAPER_REMAPPED = "plugins" + File.separator + ".paper-remapped" + File.separator
+ "unknown-origin";
/**
* This must return a new instance of the addon. It is called when the Pladdon is loaded.
@ -26,9 +30,10 @@ public abstract class Pladdon extends JavaPlugin {
@Override
public void onLoad() {
String parentFolder = getFile().getParent();
BentoBox.getInstance().logDebug("LOOK HERE: " + parentFolder);
if (parentFolder == null || !parentFolder.endsWith(ADDONS_FOLDER)) {
// Jar is in the wrong place. Let's move it
moveJar();
//moveJar();
}
}

View File

@ -520,6 +520,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Convenience method to check if a user has a team.
* Consider checking the island itself {@link Island#inTeam(UUID)}
*
* @param world - the world to check
* @param user - the User

View File

@ -46,7 +46,7 @@ public class AdminDeleteCommand extends ConfirmableCommand {
}
// Team members should be kicked before deleting otherwise the whole team will become weird
if (getIslands().inTeam(getWorld(), targetUUID) && user.getUniqueId().equals(island.getOwner())) {
if (island.hasTeam() && user.getUniqueId().equals(island.getOwner())) {
user.sendMessage("commands.admin.delete.cannot-delete-owner");
return false;
}

View File

@ -1,61 +0,0 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
public class AdminEmptyTrashCommand extends ConfirmableCommand {
/**
* Clear trash for player, or all unowned islands in trash
* @param parent - admin command
* @since 1.3.0
*/
public AdminEmptyTrashCommand(CompositeCommand parent) {
super(parent, "emptytrash");
}
@Override
public void setup() {
setPermission("admin.trash");
setOnlyPlayer(false);
setParametersHelp("commands.admin.emptytrash.parameters");
setDescription("commands.admin.emptytrash.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() > 1) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
UUID targetUUID = args.isEmpty() ? null : getPlayers().getUUID(args.get(0));
if (!args.isEmpty() && targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Remove trash for this player
final List<Island> islands = getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID);
if (islands.isEmpty()) {
if (args.isEmpty()) {
user.sendMessage("commands.admin.trash.no-unowned-in-trash");
} else {
user.sendMessage("commands.admin.trash.no-islands-in-trash");
}
return false;
} else {
this.askConfirmation(user, () -> {
getIslands().deleteQuarantinedIslandByUser(getWorld(), targetUUID);
user.sendMessage("commands.admin.emptytrash.success");
});
return true;
}
}
}

View File

@ -49,9 +49,6 @@ public class AdminInfoCommand extends CompositeCommand {
Island island = getIslands().getIsland(getWorld(), targetUUID);
if (island != null) {
new IslandInfo(island).showAdminInfo(user, getAddon());
if (!getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID).isEmpty()) {
user.sendMessage("commands.admin.info.islands-in-trash");
}
return true;
} else {
user.sendMessage("general.errors.player-has-no-island");

View File

@ -16,7 +16,6 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.flags.Flag.Mode;
import world.bentobox.bentobox.api.flags.Flag.Type;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder;
@ -206,11 +205,9 @@ public class AdminSettingsCommand extends CompositeCommand {
switch (f.getType()) {
case PROTECTION -> {
island.setFlag(f, rank);
getIslands().save(island);
}
case SETTING -> {
island.setSettingsFlag(f, activeState);
getIslands().save(island);
}
case WORLD_SETTING -> f.setSetting(getWorld(), activeState);
default -> {
@ -226,12 +223,11 @@ public class AdminSettingsCommand extends CompositeCommand {
user.sendMessage("general.errors.use-in-game");
return false;
}
getPlayers().setFlagsDisplayMode(user.getUniqueId(), Mode.EXPERT);
if (args.isEmpty()) {
new TabbedPanelBuilder()
.user(user)
.world(getWorld())
.tab(1, new SettingsTab(getWorld(), user, Flag.Type.WORLD_SETTING))
.tab(1, new SettingsTab(getWorld(), user, Flag.Type.WORLD_SETTING, Flag.Mode.EXPERT))
.tab(2, new WorldDefaultSettingsTab(getWorld(), user))
.startingSlot(1)
.size(54)
@ -242,8 +238,8 @@ public class AdminSettingsCommand extends CompositeCommand {
new TabbedPanelBuilder()
.user(user)
.world(island.getWorld())
.island(island).tab(1, new SettingsTab(user, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(user, Flag.Type.SETTING))
.island(island).tab(1, new SettingsTab(getWorld(), user, Flag.Type.PROTECTION, Flag.Mode.EXPERT))
.tab(2, new SettingsTab(getWorld(), user, Flag.Type.SETTING, Flag.Mode.EXPERT))
.startingSlot(1)
.size(54)
.build().openPanel();

View File

@ -1,87 +0,0 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang.math.NumberUtils;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
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.util.Util;
public class AdminSwitchtoCommand extends ConfirmableCommand {
private UUID targetUUID;
private @NonNull List<Island> islands;
/**
* Switch player's island to the numbered one in trash
* @param parent - admin command
* @since 1.3.0
*/
public AdminSwitchtoCommand(CompositeCommand parent) {
super(parent, "switchto");
islands = new ArrayList<>();
}
@Override
public void setup() {
setPermission("admin.switchto");
setOnlyPlayer(false);
setParametersHelp("commands.admin.switchto.parameters");
setDescription("commands.admin.switchto.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() != 2) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Check island number
islands = getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID);
if (islands.isEmpty()) {
user.sendMessage("commands.admin.trash.no-islands-in-trash");
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (NumberUtils.isDigits(args.get(1))) {
try {
int n = Integer.parseInt(args.get(1));
if (n < 1 || n > islands.size()) {
user.sendMessage("commands.admin.switchto.out-of-range", TextVariables.NUMBER, String.valueOf(islands.size()), TextVariables.LABEL, getTopLabel());
return false;
}
this.askConfirmation(user, () -> {
if (getIslands().switchIsland(getWorld(), targetUUID, islands.get(n -1))) {
user.sendMessage("commands.admin.switchto.success");
} else {
user.sendMessage("commands.admin.switchto.cannot-switch");
}
});
return true;
} catch (Exception e) {
showHelp(this, user);
return false;
}
}
return true;
}
}

View File

@ -1,73 +0,0 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.UUID;
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.util.IslandInfo;
public class AdminTrashCommand extends CompositeCommand {
/**
* A command for viewing islands in the database trash
* @param parent - admin command
* @since 1.3.0
*/
public AdminTrashCommand(CompositeCommand parent) {
super(parent, "trash");
}
@Override
public void setup() {
setPermission("admin.trash");
setOnlyPlayer(false);
setParametersHelp("commands.admin.trash.parameters");
setDescription("commands.admin.trash.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() > 1) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
UUID targetUUID = args.isEmpty() ? null : getPlayers().getUUID(args.get(0));
if (!args.isEmpty() && targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Show trash can info for this player
List<Island> islands = getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID);
if (islands.isEmpty()) {
if (args.isEmpty()) {
user.sendMessage("commands.admin.trash.no-unowned-in-trash");
} else {
user.sendMessage("commands.admin.trash.no-islands-in-trash");
}
return false;
} else {
if (targetUUID == null) {
showTrash(user, islands);
} else {
getIslands().getQuarantineCache().values().forEach(v -> showTrash(user, v));
}
return true;
}
}
private void showTrash(User user, List<Island> islands) {
user.sendMessage("commands.admin.trash.title");
for (int i = 0; i < islands.size(); i++) {
user.sendMessage("commands.admin.trash.count", TextVariables.NUMBER, String.valueOf(i+1));
new IslandInfo(islands.get(i)).showInfo(user);
}
user.sendMessage("commands.admin.trash.use-switch", TextVariables.LABEL, getTopLabel());
user.sendMessage("commands.admin.trash.use-emptytrash", TextVariables.LABEL, getTopLabel());
}
}

View File

@ -116,7 +116,6 @@ public class AdminUnregisterCommand extends ConfirmableCommand {
targetIsland.getMembers().clear();
targetIsland.log(new LogEntry.Builder("UNREGISTER").data("player", targetUUID.toString())
.data("admin", user.getUniqueId().toString()).build());
getIslands().save(targetIsland);
user.sendMessage("commands.admin.unregister.unregistered-island", TextVariables.XYZ, Util.xyz(targetIsland.getCenter().toVector()),
TextVariables.NAME, getPlayers().getName(targetUUID));
}

View File

@ -12,7 +12,6 @@ import world.bentobox.bentobox.api.commands.admin.resets.AdminResetsCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamAddCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamFixCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamKickCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamSetownerCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -63,7 +62,6 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
new AdminTeamKickCommand(this);
new AdminTeamDisbandCommand(this);
new AdminTeamSetownerCommand(this);
new AdminTeamFixCommand(this);
// Blueprints
new AdminBlueprintCommand(this);
// Register/unregister islands

View File

@ -12,6 +12,7 @@ import org.bukkit.Particle;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.commands.admin.range.AdminRangeDisplayCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.BlueprintsManager;
@ -23,7 +24,6 @@ public class AdminBlueprintCommand extends ConfirmableCommand {
// Map containing selection cuboid display tasks
private Map<User, Integer> displayClipboards;
private static final Particle PARTICLE = Particle.REDSTONE;
private static final Particle.DustOptions PARTICLE_DUST_OPTIONS = new Particle.DustOptions(Color.RED, 1.0F);
public AdminBlueprintCommand(CompositeCommand parent) {
@ -99,26 +99,38 @@ public class AdminBlueprintCommand extends ConfirmableCommand {
// Drawing x-axes
for (int x = minX; x <= maxX; x++) {
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5, maxZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5, maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5,
maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5,
maxZ + 0.5);
}
// Drawing y-axes
for (int y = minY; y <= maxY; y++) {
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5, maxZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5, maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5,
maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5,
maxZ + 0.5);
}
// Drawing z-axes
for (int z = minZ; z <= maxZ; z++) {
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, minY + 0.5, z + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, minY + 0.5, z + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, maxY + 0.5, z + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, maxY + 0.5, z + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, minY + 0.5,
z + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, minY + 0.5,
z + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, maxY + 0.5,
z + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, maxY + 0.5,
z + 0.5);
}
}

View File

@ -40,7 +40,6 @@ public class NamePrompt extends StringPrompt {
@Override
public Prompt acceptInput(@NonNull ConversationContext context, String input) {
if (island.renameHome(oldName, input)) {
plugin.getIslands().save(island);
Bukkit.getScheduler().runTask(plugin, () -> user.sendMessage("general.success"));
} else {
Bukkit.getScheduler().runTask(plugin, () -> user.sendMessage("commands.island.renamehome.already-exists"));

View File

@ -5,7 +5,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
@ -18,6 +17,7 @@ import world.bentobox.bentobox.api.events.island.IslandDeletedEvent;
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.util.Util;
public class AdminPurgeCommand extends CompositeCommand implements Listener {
@ -82,7 +82,7 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener {
user.sendMessage("commands.admin.purge.confirm", TextVariables.LABEL, this.getTopLabel());
return false;
}
} catch(Exception e) {
} catch (NumberFormatException e) {
user.sendMessage("commands.admin.purge.number-error");
return false;
}
@ -120,29 +120,42 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener {
}
}
/**
* Gets a set of islands that are older than the parameter in days
* @param days days
* @return set of islands
*/
Set<String> getOldIslands(int days) {
long currentTimeMillis = System.currentTimeMillis();
double daysInMilliseconds = days * 1000 * 3600 * 24;
Set<String> oldIslands = new HashSet<>();
// Process islands in one pass, logging and adding to the set if applicable
getPlugin().getIslands().getIslands().stream()
.filter(i -> !i.isSpawn())
.filter(i -> !i.getPurgeProtected())
.filter(i -> i.getWorld().equals(this.getWorld()))
.filter(Island::isOwned)
.filter(i -> i.getMembers().size() == 1)
.filter(i -> ((double)(System.currentTimeMillis() - Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed()) / 1000 / 3600 / 24) > days)
.forEach(i -> {
Date date = new Date(Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed());
BentoBox.getInstance().log("Will purge " +
BentoBox.getInstance().getPlayers().getName(i.getOwner()) +
" last logged in " + (int)((double)(System.currentTimeMillis() - Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed()) / 1000 / 3600 / 24) + " days ago. " + date);
});
return getPlugin().getIslands().getIslands().stream()
.filter(i -> !i.isSpawn())
.filter(i -> !i.getPurgeProtected())
.filter(i -> i.getWorld().equals(this.getWorld()))
.filter(Island::isOwned)
.filter(i -> i.getMembers().size() == 1)
.filter(i -> ((double)(System.currentTimeMillis() - Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed()) / 1000 / 3600 / 24) > days)
.map(Island::getUniqueId)
.collect(Collectors.toSet());
.filter(i -> !i.isSpawn()).filter(i -> !i.getPurgeProtected())
.filter(i -> i.getWorld().equals(this.getWorld())).filter(Island::isOwned).filter(
i -> i.getMemberSet().stream()
.allMatch(member -> (currentTimeMillis
- Bukkit.getOfflinePlayer(member).getLastPlayed()) > daysInMilliseconds))
.forEach(i -> {
// Add the unique island ID to the set
oldIslands.add(i.getUniqueId());
BentoBox.getInstance().log("Will purge island at " + Util.xyz(i.getCenter().toVector()) + " in "
+ i.getWorld().getName());
// Log each member's last login information
i.getMemberSet().forEach(member -> {
Date lastLogin = new Date(Bukkit.getOfflinePlayer(member).getLastPlayed());
BentoBox.getInstance()
.log("Player " + BentoBox.getInstance().getPlayers().getName(member)
+ " last logged in "
+ (int) ((currentTimeMillis - Bukkit.getOfflinePlayer(member).getLastPlayed())
/ 1000 / 3600 / 24)
+ " days ago. " + lastLogin);
});
BentoBox.getInstance().log("+-----------------------------------------+");
});
return oldIslands;
}
/**

View File

@ -0,0 +1,103 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
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.util.Util;
/**
* @author Poslovitch
*/
public abstract class AbstractAdminRangeCommand extends CompositeCommand {
protected @Nullable UUID targetUUID;
protected Island targetIsland;
public AbstractAdminRangeCommand(CompositeCommand parent, String string) {
super(parent, string);
}
@Override
public boolean canExecute(User user, String label, @NonNull List<String> args) {
if (args.size() <= 1) {
showHelp(this, user);
return false;
}
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
return false;
}
// Check if the player has more than one island
Map<String, Island> islands = getIslandsXYZ(targetUUID);
if (islands.size() == 0) {
user.sendMessage("general.errors.player-has-no-island");
return false;
} else if (args.size() == 2) {
// If they only have one island, 2 args are fine
if (islands.size() == 1) {
targetIsland = islands.values().iterator().next();
return true;
} else {
// They need to specify which island
user.sendMessage("commands.admin.unregister.errors.player-has-more-than-one-island");
user.sendMessage("commands.admin.unregister.errors.specify-island-location");
return false;
}
} else if (args.size() != 3) {
// No location
user.sendMessage("commands.admin.unregister.errors.specify-island-location");
return false;
} else if (!islands.containsKey(args.get(2))) {
if (args.get(2).equalsIgnoreCase("help")) {
this.showHelp(this, user);
return false;
}
user.sendMessage("commands.admin.unregister.errors.unknown-island-location");
return false;
}
targetIsland = islands.get(args.get(2));
return true;
}
protected Map<String, Island> getIslandsXYZ(UUID target) {
return getIslands().getOwnedIslands(getWorld(), target).stream()
.collect(Collectors.toMap(island -> Util.xyz(island.getCenter().toVector()), island -> island));
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
} else if (args.size() == 3) {
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
} else if (args.size() > 4) {
// Find out which user
UUID uuid = getPlayers().getUUID(args.get(2));
if (uuid != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(getIslandsXYZ(uuid).keySet()), lastArg));
}
}
return Optional.empty();
}
}

View File

@ -1,23 +1,18 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
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.util.Util;
/**
* @since 1.10.0
* @author Poslovitch
*/
public class AdminRangeAddCommand extends CompositeCommand {
public class AdminRangeAddCommand extends AbstractAdminRangeCommand {
public AdminRangeAddCommand(AdminRangeCommand parent) {
super(parent, "add");
@ -32,53 +27,28 @@ public class AdminRangeAddCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, @NonNull List<String> args) {
if (args.size() != 2) {
showHelp(this, user);
return false;
}
int newRange = targetIsland.getProtectionRange() + Integer.parseInt(args.get(1));
UUID targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
if (newRange > targetIsland.getRange()) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER,
String.valueOf(targetIsland.getRange()));
return false;
}
if (!(getIslands().hasIsland(getWorld(), targetUUID) || getIslands().inTeam(getWorld(), targetUUID))) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
return false;
}
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID));
int newRange = island.getProtectionRange() + Integer.parseInt(args.get(1));
if (newRange > island.getRange()) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER, String.valueOf(island.getRange()));
return false;
} else if (newRange == island.getProtectionRange()) {
} else if (newRange == targetIsland.getProtectionRange()) {
user.sendMessage("commands.admin.range.invalid-value.same-as-before", TextVariables.NUMBER, args.get(1));
return false;
}
// Get old range for event
int oldRange = island.getProtectionRange();
int oldRange = targetIsland.getProtectionRange();
// Well, now it can be applied without taking any risks!
island.setProtectionRange(newRange);
targetIsland.setProtectionRange(newRange);
// Call Protection Range Change event. Does not support cancelling.
IslandEvent.builder()
.island(island)
.location(island.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE)
.involvedPlayer(targetUUID)
.admin(true)
.protectionRange(newRange, oldRange)
.build();
.island(targetIsland).location(targetIsland.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE).involvedPlayer(targetUUID).admin(true)
.protectionRange(newRange, oldRange).build();
user.sendMessage("commands.admin.range.add.success",
TextVariables.NAME, args.get(0), TextVariables.NUMBER, args.get(1),
@ -86,4 +56,6 @@ public class AdminRangeAddCommand extends CompositeCommand {
return true;
}
}

View File

@ -31,4 +31,5 @@ public class AdminRangeCommand extends CompositeCommand {
showHelp(this, user);
return true;
}
}
}

View File

@ -13,6 +13,7 @@ import org.bukkit.Particle;
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.util.Util;
/**
* @author Poslovitch
@ -23,6 +24,9 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
private static final String DISPLAY = "display";
private static final String SHOW = "show";
private static final String HIDE = "hide";
public static final Particle PARTICLE = Util.findFirstMatchingEnum(Particle.class, "REDSTONE", "DUST");
private static final Particle PARTICLE2 = Util.findFirstMatchingEnum(Particle.class, "VILLAGER_HAPPY",
"HAPPY_VILLAGER");
// Map of users to which ranges must be displayed
private final Map<User, Integer> displayRanges = new HashMap<>();
@ -76,11 +80,11 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
// Draw the default protected area if island protected zone is different
if (island.getProtectionRange() != getPlugin().getIWM().getIslandProtectionRange(getWorld())) {
drawZone(user, Particle.VILLAGER_HAPPY, null, island, getPlugin().getIWM().getIslandProtectionRange(getWorld()));
drawZone(user, PARTICLE2, null, island, getPlugin().getIWM().getIslandProtectionRange(getWorld()));
}
// Draw the island area
drawZone(user, Particle.REDSTONE, new Particle.DustOptions(Color.GRAY, 1.0F), island, island.getRange());
drawZone(user, PARTICLE, new Particle.DustOptions(Color.GRAY, 1.0F), island, island.getRange());
});
}, 20, 30));
}

View File

@ -1,23 +1,19 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
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.util.Util;
/**
* @since 1.10.0
* @author Poslovitch
*/
public class AdminRangeRemoveCommand extends CompositeCommand {
public class AdminRangeRemoveCommand extends AbstractAdminRangeCommand {
public AdminRangeRemoveCommand(AdminRangeCommand parent) {
super(parent, "remove");
@ -32,48 +28,31 @@ public class AdminRangeRemoveCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, @NonNull List<String> args) {
if (args.size() != 2) {
showHelp(this, user);
return false;
}
UUID targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!(getIslands().hasIsland(getWorld(), targetUUID) || getIslands().inTeam(getWorld(), targetUUID))) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
return false;
}
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID));
int newRange = island.getProtectionRange() - Integer.parseInt(args.get(1));
int newRange = targetIsland.getProtectionRange() - Integer.parseInt(args.get(1));
if (newRange <= 1) {
user.sendMessage("commands.admin.range.invalid-value.too-low", TextVariables.NUMBER, String.valueOf(island.getRange()));
user.sendMessage("commands.admin.range.invalid-value.too-low", TextVariables.NUMBER,
String.valueOf(targetIsland.getRange()));
return false;
} else if (newRange == island.getProtectionRange()) {
} else if (newRange == targetIsland.getProtectionRange()) {
user.sendMessage("commands.admin.range.invalid-value.same-as-before", TextVariables.NUMBER, args.get(1));
return false;
}
// Get old range for event
int oldRange = island.getProtectionRange();
int oldRange = targetIsland.getProtectionRange();
// Well, now it can be applied without taking any risks!
island.setProtectionRange(newRange);
targetIsland.setProtectionRange(newRange);
// Call Protection Range Change event. Does not support cancelling.
IslandEvent.builder()
.island(island)
.location(island.getCenter())
.island(targetIsland).location(targetIsland.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE)
.involvedPlayer(targetUUID)
.admin(true)

View File

@ -1,19 +1,14 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
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.util.Util;
public class AdminRangeSetCommand extends CompositeCommand {
public class AdminRangeSetCommand extends AbstractAdminRangeCommand {
public AdminRangeSetCommand(CompositeCommand parent) {
super(parent, "set");
@ -28,23 +23,6 @@ public class AdminRangeSetCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 2) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
UUID targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!(getIslands().hasIsland(getWorld(), targetUUID) || getIslands().inTeam(getWorld(), targetUUID))) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
// Get new range
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
@ -52,33 +30,30 @@ public class AdminRangeSetCommand extends CompositeCommand {
}
int range = Integer.parseInt(args.get(1));
// Get island
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID));
// Do some sanity checks to make sure the new protection range won't cause problems
if (range < 1) {
user.sendMessage("commands.admin.range.invalid-value.too-low", TextVariables.NUMBER, args.get(1));
return false;
}
if (range > island.getRange() * 2) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER, String.valueOf(2 * island.getRange()));
if (range > targetIsland.getRange() * 2) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER,
String.valueOf(2 * targetIsland.getRange()));
return false;
}
if (range == island.getProtectionRange()) {
if (range == targetIsland.getProtectionRange()) {
user.sendMessage("commands.admin.range.invalid-value.same-as-before", TextVariables.NUMBER, args.get(1));
return false;
}
// Get old range for event
int oldRange = island.getProtectionRange();
int oldRange = targetIsland.getProtectionRange();
// Well, now it can be applied without taking any risks!
island.setProtectionRange(range);
targetIsland.setProtectionRange(range);
// Call Protection Range Change event. Does not support canceling.
IslandEvent.builder()
.island(island)
.location(island.getCenter())
.island(targetIsland).location(targetIsland.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE)
.involvedPlayer(targetUUID)
.admin(true)
@ -90,14 +65,4 @@ public class AdminRangeSetCommand extends CompositeCommand {
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -44,17 +44,17 @@ public class AdminTeamAddCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(1));
return false;
}
if (!getIslands().hasIsland(getWorld(), ownerUUID)) {
Island island = getIslands().getPrimaryIsland(getWorld(), ownerUUID);
if (island == null || !getIslands().hasIsland(getWorld(), ownerUUID)) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
Island island = getIslands().getPrimaryIsland(getWorld(), ownerUUID);
if (getIslands().inTeam(getWorld(), ownerUUID) && island != null && !ownerUUID.equals(island.getOwner())) {
if (getIslands().inTeam(getWorld(), ownerUUID) && !ownerUUID.equals(island.getOwner())) {
user.sendMessage("commands.admin.team.add.name-not-owner", TextVariables.NAME, args.get(0));
new IslandInfo(island).showMembers(user);
return false;
}
if (getIslands().inTeam(getWorld(), targetUUID)) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands() && island.inTeam(targetUUID)) {
user.sendMessage("commands.island.team.invite.errors.already-on-team");
return false;
}

View File

@ -52,10 +52,7 @@ public class AdminTeamDisbandCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.player-is-not-owner", TextVariables.NAME, args.get(0));
return false;
}
// Find the island the player is an owner of
Map<String, Island> islands = getIslandsXYZ(targetUUID);
if (islands.isEmpty()) {
@ -77,6 +74,11 @@ public class AdminTeamDisbandCommand extends CompositeCommand {
// Get the only island
island = islands.values().iterator().next();
}
// Check that the target owns the island
if (island.getOwner() == null || !island.getOwner().equals(targetUUID)) {
user.sendMessage("general.errors.player-is-not-owner", TextVariables.NAME, args.get(0));
return false;
}
return true;
}

View File

@ -1,35 +0,0 @@
package world.bentobox.bentobox.api.commands.admin.team;
import java.util.List;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
public class AdminTeamFixCommand extends CompositeCommand {
public AdminTeamFixCommand(CompositeCommand parent) {
super(parent, "fix");
}
@Override
public void setup() {
setPermission("mod.team.fix");
setDescription("commands.admin.team.fix.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (!args.isEmpty()) {
showHelp(this, user);
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
getIslands().checkTeams(user, getWorld());
return true;
}
}

View File

@ -70,7 +70,7 @@ public class IslandBanCommand extends CompositeCommand {
user.sendMessage("commands.island.ban.cannot-ban-yourself");
return false;
}
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("commands.island.ban.cannot-ban-member");
return false;
}

View File

@ -51,7 +51,8 @@ public class IslandCreateCommand extends CompositeCommand {
}
}
// Check if this player is on a team in this world
if (getIslands().inTeam(getWorld(), user.getUniqueId()) && island != null
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().inTeam(getWorld(), user.getUniqueId()) && island != null
&& !user.getUniqueId().equals(island.getOwner())) {
// Team members who are not owners cannot make additional islands
user.sendMessage("commands.island.create.you-cannot-make-team");
@ -86,22 +87,49 @@ public class IslandCreateCommand extends CompositeCommand {
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
// Check perm
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) {
return false;
}
// Check maximum uses
if (checkMaxUses(user, name)) {
return false;
}
// Make island
return makeIsland(user, name);
} else {
if (getPlugin().getSettings().getIslandNumber() > 1
&& checkMaxUses(user, BlueprintsManager.DEFAULT_BUNDLE_NAME)) {
return false;
}
// Show panel only if there are multiple bundles available
if (getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).size() > 1) {
// Show panel
IslandCreationPanel.openPanel(this, user, label);
IslandCreationPanel.openPanel(this, user, label, false);
return true;
}
return makeIsland(user, BlueprintsManager.DEFAULT_BUNDLE_NAME);
}
}
private boolean checkMaxUses(User user, String name) {
if (getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).containsKey(name)) {
int maxTimes = getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).get(name).getTimes();
// Check how many times this player has used this bundle
if (maxTimes > 0 && getBundleUses(user, name) >= maxTimes) {
user.sendMessage("commands.island.create.max-uses");
return true;
}
}
return false;
}
private long getBundleUses(User user, String name) {
return getIslands().getIslands(getWorld(), user).stream()
.filter(is -> is.getMetaData("bundle").map(mdv -> name.equalsIgnoreCase(mdv.asString())).orElse(false))
.count();
}
private boolean makeIsland(User user, String name) {
user.sendMessage("commands.island.create.creating-island");
try {

View File

@ -76,7 +76,7 @@ public class IslandExpelCommand extends CompositeCommand {
return false;
}
// Or team member
if (island.getMemberSet().contains(targetUUID)) {
if (island.inTeam(targetUUID)) {
user.sendMessage("commands.island.expel.cannot-expel-member");
return false;
}

View File

@ -6,7 +6,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.DelayedTeleportCommand;
@ -41,7 +40,7 @@ public class IslandGoCommand extends DelayedTeleportCommand {
user.sendMessage("commands.island.go.teleport");
return false;
}
Set<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
List<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
if (islands.isEmpty()) {
user.sendMessage("general.errors.no-island");
return false;
@ -86,7 +85,7 @@ public class IslandGoCommand extends DelayedTeleportCommand {
return true;
}
private boolean checkReserved(User user, Set<Island> islands) {
private boolean checkReserved(User user, List<Island> islands) {
for (Island island : islands) {
if (island.isReserved()) {
// Send player to create an island

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.island;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
@ -14,7 +13,7 @@ import world.bentobox.bentobox.util.Util;
public class IslandHomesCommand extends ConfirmableCommand {
private Set<Island> islands;
private List<Island> islands;
public IslandHomesCommand(CompositeCommand islandCommand) {
super(islandCommand, "homes");

View File

@ -111,7 +111,7 @@ public class IslandResetCommand extends ConfirmableCommand {
// Show panel only if there are multiple bundles available
if (getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).size() > 1) {
// Show panel - once the player selected a bundle, this will re-run this command
IslandCreationPanel.openPanel(this, user, label);
IslandCreationPanel.openPanel(this, user, label, true);
} else {
resetIsland(user, BlueprintsManager.DEFAULT_BUNDLE_NAME);
}

View File

@ -49,7 +49,8 @@ public class IslandSettingsCommand extends CompositeCommand {
.user(user)
.island(island)
.world(island.getWorld())
.tab(1, new SettingsTab(user, Flag.Type.PROTECTION)).tab(2, new SettingsTab(user, Flag.Type.SETTING))
.tab(1, new SettingsTab(getWorld(), user, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(getWorld(), user, Flag.Type.SETTING))
.startingSlot(1)
.size(54)
.hideIfEmpty()

View File

@ -1,95 +0,0 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.util.Objects;
import java.util.UUID;
import world.bentobox.bentobox.database.objects.Island;
/**
* Represents an invite
* @author tastybento
* @since 1.8.0
*/
public class Invite {
/**
* Type of invitation
*
*/
public enum Type {
COOP,
TEAM,
TRUST
}
private final Type type;
private final UUID inviter;
private final UUID invitee;
private final Island island;
/**
* @param type - invitation type, e.g., coop, team, trust
* @param inviter - UUID of inviter
* @param invitee - UUID of invitee
* @param island - the island this invite is for
*/
public Invite(Type type, UUID inviter, UUID invitee, Island island) {
this.type = type;
this.inviter = inviter;
this.invitee = invitee;
this.island = island;
}
/**
* @return the type
*/
public Type getType() {
return type;
}
/**
* @return the inviter
*/
public UUID getInviter() {
return inviter;
}
/**
* @return the invitee
*/
public UUID getInvitee() {
return invitee;
}
/**
* @return the island
*/
public Island getIsland() {
return island;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(invitee, inviter, type);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Invite other)) {
return false;
}
return Objects.equals(invitee, other.invitee) && Objects.equals(inviter, other.inviter) && type == other.type;
}
}

View File

@ -1,9 +1,7 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -16,17 +14,13 @@ import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.TeamInvite;
import world.bentobox.bentobox.managers.RanksManager;
public class IslandTeamCommand extends CompositeCommand {
/**
* Invited list. Key is the invited party, value is the invite.
* @since 1.8.0
*/
private final Map<UUID, Invite> inviteMap;
private IslandTeamKickCommand kickCommand;
private IslandTeamLeaveCommand leaveCommand;
@ -51,9 +45,11 @@ public class IslandTeamCommand extends CompositeCommand {
private IslandTeamTrustCommand trustCommand;
private final Database<TeamInvite> handler;
public IslandTeamCommand(CompositeCommand parent) {
super(parent, "team");
inviteMap = new HashMap<>();
handler = new Database<>(parent.getAddon(), TeamInvite.class);
}
@Override
@ -139,8 +135,8 @@ public class IslandTeamCommand extends CompositeCommand {
* @param invitee - uuid of invitee
* @since 1.8.0
*/
public void addInvite(Invite.Type type, @NonNull UUID inviter, @NonNull UUID invitee, @NonNull Island island) {
inviteMap.put(invitee, new Invite(type, inviter, invitee, island));
public void addInvite(TeamInvite.Type type, @NonNull UUID inviter, @NonNull UUID invitee, @NonNull Island island) {
handler.saveObjectAsync(new TeamInvite(type, inviter, invitee, island.getUniqueId()));
}
/**
@ -150,7 +146,7 @@ public class IslandTeamCommand extends CompositeCommand {
* @since 1.8.0
*/
public boolean isInvited(@NonNull UUID invitee) {
return inviteMap.containsKey(invitee);
return handler.objectExists(invitee.toString());
}
/**
@ -161,7 +157,7 @@ public class IslandTeamCommand extends CompositeCommand {
*/
@Nullable
public UUID getInviter(UUID invitee) {
return isInvited(invitee) ? inviteMap.get(invitee).getInviter() : null;
return isInvited(invitee) ? handler.loadObject(invitee.toString()).getInviter() : null;
}
/**
@ -171,8 +167,8 @@ public class IslandTeamCommand extends CompositeCommand {
* @since 1.8.0
*/
@Nullable
public Invite getInvite(UUID invitee) {
return inviteMap.get(invitee);
public TeamInvite getInvite(UUID invitee) {
return handler.loadObject(invitee.toString());
}
/**
@ -181,7 +177,7 @@ public class IslandTeamCommand extends CompositeCommand {
* @since 1.8.0
*/
public void removeInvite(@NonNull UUID invitee) {
inviteMap.remove(invitee);
handler.deleteID(invitee.toString());
}
/**

View File

@ -8,10 +8,10 @@ import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
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.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -99,7 +99,7 @@ public class IslandTeamCoopCommand extends CompositeCommand {
// Put the invited player (key) onto the list with inviter (value)
// If someone else has invited a player, then this invite will overwrite the
// previous invite!
itc.addInvite(Invite.Type.COOP, user.getUniqueId(), target.getUniqueId(), island);
itc.addInvite(Type.COOP, user.getUniqueId(), target.getUniqueId(), island);
user.sendMessage("commands.island.team.invite.invitation-sent", TextVariables.NAME, target.getName());
// Send message to online player
target.sendMessage("commands.island.team.coop.name-has-invited-you", TextVariables.NAME,

View File

@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
@ -34,6 +33,8 @@ import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecord
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.TeamInvite;
import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -208,7 +209,7 @@ public class IslandTeamGUI {
private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
PanelItemBuilder builder = new PanelItemBuilder();
if (parent.isInvited(user.getUniqueId()) && user.hasPermission(parent.getAcceptCommand().getPermission())) {
Invite invite = parent.getInvite(user.getUniqueId());
TeamInvite invite = parent.getInvite(user.getUniqueId());
if (invite == null) {
return this.getBlankBorder();
}
@ -224,7 +225,8 @@ public class IslandTeamGUI {
return builder.build();
}
private void createInviteClickHandler(PanelItemBuilder builder, Invite invite, @NonNull List<ActionRecords> list) {
private void createInviteClickHandler(PanelItemBuilder builder, TeamInvite invite,
@NonNull List<ActionRecords> list) {
Type type = invite.getType();
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
@ -526,7 +528,6 @@ public class IslandTeamGUI {
/**
* Creates text to describe the status of the player
* @param user2 user asking to see the status
* @param offlineMember member of the team
* @return string
*/

View File

@ -1,18 +1,18 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
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.database.objects.TeamInvite;
import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -50,7 +50,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
user.sendMessage(INVALID_INVITE);
return false;
}
Invite invite = itc.getInvite(playerUUID);
TeamInvite invite = itc.getInvite(playerUUID);
if (invite.getType().equals(Type.TEAM)) {
// Check rank to of inviter
Island island = getIslands().getIsland(getWorld(), prospectiveOwnerUUID);
@ -62,7 +62,8 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
}
// Check if player is already in a team
if (getIslands().inTeam(getWorld(), playerUUID)) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().inTeam(getWorld(), playerUUID)) {
user.sendMessage("commands.island.team.invite.errors.you-already-are-in-team");
return false;
}
@ -78,21 +79,27 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
// Get the invite
Invite invite = itc.getInvite(user.getUniqueId());
TeamInvite invite = itc.getInvite(user.getUniqueId());
switch (invite.getType()) {
case COOP -> askConfirmation(user, () -> acceptCoopInvite(user, invite));
case TRUST -> askConfirmation(user, () -> acceptTrustInvite(user, invite));
default -> askConfirmation(user, user.getTranslation("commands.island.team.invite.accept.confirmation"),
() -> acceptTeamInvite(user, invite));
default -> {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()) {
askConfirmation(user, user.getTranslation("commands.island.team.invite.accept.confirmation"),
() -> acceptTeamInvite(user, invite));
} else {
acceptTeamInvite(user, invite);
}
}
}
return true;
}
void acceptTrustInvite(User user, Invite invite) {
void acceptTrustInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(user.getUniqueId());
User inviter = User.getInstance(invite.getInviter());
Island island = invite.getIsland();
Island island = getIslands().getIslandById(invite.getIslandID()).orElse(null);
if (island != null) {
if (island.getMemberSet(RanksManager.TRUSTED_RANK, false).size() > getIslands().getMaxMembers(island,
RanksManager.TRUSTED_RANK)) {
@ -114,11 +121,11 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
}
}
void acceptCoopInvite(User user, Invite invite) {
void acceptCoopInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(user.getUniqueId());
User inviter = User.getInstance(invite.getInviter());
Island island = invite.getIsland();
Island island = getIslands().getIslandById(invite.getIslandID()).orElse(null);
if (island != null) {
if (island.getMemberSet(RanksManager.COOP_RANK, false).size() > getIslands().getMaxMembers(island,
RanksManager.COOP_RANK)) {
@ -140,13 +147,13 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
}
}
void acceptTeamInvite(User user, Invite invite) {
void acceptTeamInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(user.getUniqueId());
// Get the player's island - may be null if the player has no island
Set<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
List<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
// Get the team's island
Island teamIsland = invite.getIsland();
Island teamIsland = getIslands().getIslandById(invite.getIslandID()).orElse(null);
if (teamIsland == null) {
user.sendMessage(INVALID_INVITE);
return;
@ -156,17 +163,21 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
user.sendMessage("commands.island.team.invite.errors.island-is-full");
return;
}
// Remove the player's other islands
getIslands().removePlayer(getWorld(), user.getUniqueId());
// Remove money inventory etc. for leaving
cleanPlayer(user);
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()) {
// Remove the player's other islands
getIslands().removePlayer(getWorld(), user.getUniqueId());
// Remove money inventory etc. for leaving
cleanPlayer(user);
}
// Add the player as a team member of the new island
getIslands().setJoinTeam(teamIsland, user.getUniqueId());
// Move player to team's island
getIslands().setPrimaryIsland(user.getUniqueId(), teamIsland);
getIslands().homeTeleportAsync(getWorld(), user.getPlayer()).thenRun(() -> {
// Delete the old islands
islands.forEach(island -> getIslands().deleteIsland(island, true, user.getUniqueId()));
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()) {
// Delete the old islands
islands.forEach(island -> getIslands().deleteIsland(island, true, user.getUniqueId()));
}
// Put player back into normal mode
user.setGameMode(getIWM().getDefaultGameMode(getWorld()));
@ -175,8 +186,9 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
Util.runCommands(user, ownerName, getIWM().getOnJoinCommands(getWorld()), "join");
});
// Reset deaths
if (getIWM().isTeamJoinDeathReset(getWorld())) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIWM().isTeamJoinDeathReset(getWorld())) {
// Reset deaths
getPlayers().setDeaths(getWorld(), user.getUniqueId(), 0);
}
user.sendMessage("commands.island.team.invite.accept.you-joined-island", TextVariables.LABEL, getTopLabel());
@ -185,7 +197,6 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME,
user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
}
getIslands().save(teamIsland);
// Fire event
TeamEvent.builder().island(teamIsland).reason(TeamEvent.Reason.JOINED).involvedPlayer(user.getUniqueId())
.build();

View File

@ -10,13 +10,13 @@ import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.bentobox.managers.RanksManager;
@ -105,7 +105,8 @@ public class IslandTeamInviteCommand extends CompositeCommand {
}
// Player cannot invite someone already on a team
if (getIslands().inTeam(getWorld(), invitedPlayerUUID)) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().inTeam(getWorld(), invitedPlayerUUID)) {
user.sendMessage("commands.island.team.invite.errors.already-on-team");
return false;
}
@ -165,12 +166,13 @@ public class IslandTeamInviteCommand extends CompositeCommand {
}
// Put the invited player (key) onto the list with inviter (value)
// If someone else has invited a player, then this invite will overwrite the previous invite!
itc.addInvite(Invite.Type.TEAM, user.getUniqueId(), invitedPlayer.getUniqueId(), island);
itc.addInvite(Type.TEAM, user.getUniqueId(), invitedPlayer.getUniqueId(), island);
user.sendMessage("commands.island.team.invite.invitation-sent", TextVariables.NAME, invitedPlayer.getName(), TextVariables.DISPLAY_NAME, invitedPlayer.getDisplayName());
// Send message to online player
invitedPlayer.sendMessage("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
invitedPlayer.sendMessage("commands.island.team.invite.to-accept-or-reject", TextVariables.LABEL, getTopLabel());
if (getIslands().hasIsland(getWorld(), invitedPlayer.getUniqueId())) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().hasIsland(getWorld(), invitedPlayer.getUniqueId())) {
invitedPlayer.sendMessage("commands.island.team.invite.you-will-lose-your-island");
}
return true;

View File

@ -21,7 +21,6 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;

View File

@ -63,7 +63,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
user.sendMessage("commands.island.team.kick.cannot-kick");
return false;
}
if (!getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (!getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("general.errors.not-in-team");
return false;
}
@ -95,7 +95,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
return false;
}
User target = User.getInstance(targetUUID);
Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID)); // Should never be
Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), user)); // Should never be
// null because of
// checks above
// Fire event
@ -108,7 +108,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
getAddon().getDescription().getName(), TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME,
user.getDisplayName());
getIslands().removePlayer(getWorld(), targetUUID);
getIslands().removePlayer(oldIsland, targetUUID);
// Clean the target player
getPlayers().cleanLeavingPlayer(getWorld(), target, true, oldIsland);

View File

@ -69,7 +69,7 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
return false;
}
// Check that target is a member of this island
if (!island.getMemberSet().contains(target.getUniqueId())) {
if (!island.inTeam(target.getUniqueId())) {
user.sendMessage("commands.island.team.promote.errors.must-be-member");
return false;
}

View File

@ -14,6 +14,7 @@ import world.bentobox.bentobox.api.events.team.TeamEvent;
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.IslandsManager;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -42,7 +43,7 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
}
// Can use if in a team
Island is = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (is == null || !is.getMemberSet().contains(user.getUniqueId())) {
if (is == null || !is.inTeam(user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return false;
}
@ -60,7 +61,7 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
user.sendMessage("commands.island.team.setowner.errors.cant-transfer-to-yourself");
return false;
}
if (!is.getMemberSet().contains(targetUUID)) {
if (!is.inTeam(targetUUID)) {
user.sendMessage("commands.island.team.setowner.errors.target-is-not-member");
return false;
}
@ -91,7 +92,6 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK)
.build();
getIslands().save(island);
return true;
}

View File

@ -8,10 +8,10 @@ import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
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.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;

View File

@ -74,7 +74,7 @@ public class IslandTeamUncoopCommand extends CompositeCommand {
user.sendMessage("commands.island.team.uncoop.cannot-uncoop-yourself");
return false;
}
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("commands.island.team.uncoop.cannot-uncoop-member");
return false;
}

View File

@ -74,7 +74,7 @@ public class IslandTeamUntrustCommand extends CompositeCommand {
user.sendMessage("commands.island.team.untrust.cannot-untrust-yourself");
return false;
}
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("commands.island.team.untrust.cannot-untrust-member");
return false;
}

View File

@ -644,4 +644,13 @@ public interface WorldSettings extends ConfigObject {
default int getConcurrentIslands() {
return BentoBox.getInstance().getSettings().getIslandNumber();
}
/**
* Remove islands when players join a team and not allow players to have other islands if they are in a team.
* @return true or false
* @since 2.3.0
*/
default boolean isDisallowTeamMemberIslands() {
return true;
}
}

View File

@ -388,21 +388,24 @@ public class Flag implements Comparable<Flag> {
if (!user.isOp() && invisible) {
return null;
}
// Start the flag conversion
PanelItemBuilder pib = new PanelItemBuilder()
.icon(ItemParser.parse(user.getTranslationOrNothing(this.getIconReference()), new ItemStack(icon)))
.name(user.getTranslation("protection.panel.flag-item.name-layout", TextVariables.NAME, user.getTranslation(getNameReference())))
.name(user.getTranslation("protection.panel.flag-item.name-layout", TextVariables.NAME,
user.getTranslation(getNameReference())))
.clickHandler(clickHandler)
.invisible(invisible);
if (hasSubPanel()) {
pib.description(user.getTranslation("protection.panel.flag-item.menu-layout", TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
return pib.build();
}
return switch (getType()) {
case PROTECTION -> createProtectionFlag(plugin, user, island, pib).build();
case SETTING -> createSettingFlag(user, island, pib).build();
case WORLD_SETTING -> createWorldSettingFlag(user, world, pib).build();
};
}
private PanelItemBuilder createWorldSettingFlag(User user, World world, PanelItemBuilder pib) {
@ -429,19 +432,24 @@ public class Flag implements Comparable<Flag> {
private PanelItemBuilder createProtectionFlag(BentoBox plugin, User user, Island island, PanelItemBuilder pib) {
if (island != null) {
int y = island.getFlag(this);
// Protection flag
pib.description(user.getTranslation("protection.panel.flag-item.description-layout",
TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
RanksManager.getInstance().getRanks().forEach((reference, score) -> {
if (score > RanksManager.BANNED_RANK && score < island.getFlag(this)) {
if (score > RanksManager.BANNED_RANK && score < y) {
pib.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + user.getTranslation(reference));
} else if (score <= RanksManager.OWNER_RANK && score > island.getFlag(this)) {
} else if (score <= RanksManager.OWNER_RANK && score > y) {
pib.description(user.getTranslation("protection.panel.flag-item.allowed-rank") + user.getTranslation(reference));
} else if (score == island.getFlag(this)) {
} else if (score == y) {
pib.description(user.getTranslation("protection.panel.flag-item.minimal-rank") + user.getTranslation(reference));
}
});
}
return pib;
}
@ -469,7 +477,7 @@ public class Flag implements Comparable<Flag> {
public Set<Flag> getSubflags() {
return subflags;
}
/**
* Set the name of this flag for a specified locale. This enables the flag's name to be assigned via API. It will not be stored anywhere
* and must be rewritten using this call every time the flag is built.
@ -482,7 +490,7 @@ public class Flag implements Comparable<Flag> {
public boolean setTranslatedName(Locale locale, String name) {
return BentoBox.getInstance().getLocalesManager().setTranslation(locale, getNameReference(), name);
}
/**
* Set the name of this flag for a specified locale. This enables the flag's name to be assigned via API. It will not be stored anywhere
* and must be rewritten using this call every time the flag is built.

View File

@ -4,6 +4,7 @@ import java.util.Objects;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.BentoBox;
@ -59,6 +60,11 @@ public class CycleClick implements PanelItem.ClickHandler {
@Override
public boolean onClick(Panel panel, User user2, ClickType click, int slot) {
if (panel.getWorld().isEmpty()) {
plugin.logError("Panel " + panel.getName()
+ " has no world associated with it. Please report this bug to the author.");
return true;
}
// This click listener is used with TabbedPanel and SettingsTabs only
TabbedPanel tp = (TabbedPanel)panel;
SettingsTab st = (SettingsTab)tp.getActiveTab();
@ -67,7 +73,7 @@ public class CycleClick implements PanelItem.ClickHandler {
this.user = user2;
changeOccurred = false;
// Permission prefix
String prefix = plugin.getIWM().getPermissionPrefix(Util.getWorld(user.getWorld()));
String prefix = plugin.getIWM().getPermissionPrefix(Util.getWorld(panel.getWorld().get()));
String reqPerm = prefix + "settings." + id;
String allPerms = prefix + "settings.*";
if (!user.hasPermission(reqPerm) && !user.hasPermission(allPerms)
@ -91,7 +97,7 @@ public class CycleClick implements PanelItem.ClickHandler {
rightClick(flag, currentRank);
} else if (click.equals(ClickType.SHIFT_LEFT) && user2.isOp()) {
leftShiftClick(flag);
leftShiftClick(flag, panel.getWorld().get());
}
});
} else {
@ -149,16 +155,16 @@ public class CycleClick implements PanelItem.ClickHandler {
}
private void leftShiftClick(Flag flag) {
if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(flag.getID())) {
plugin.getIWM().getHiddenFlags(user.getWorld()).add(flag.getID());
private void leftShiftClick(Flag flag, World world) {
if (!plugin.getIWM().getHiddenFlags(world).contains(flag.getID())) {
plugin.getIWM().getHiddenFlags(world).add(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
} else {
plugin.getIWM().getHiddenFlags(user.getWorld()).remove(flag.getID());
plugin.getIWM().getHiddenFlags(world).remove(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1F, 1F);
}
// Save changes
plugin.getIWM().getAddon(user.getWorld()).ifPresent(GameModeAddon::saveWorldSettings);
plugin.getIWM().getAddon(world).ifPresent(GameModeAddon::saveWorldSettings);
}

View File

@ -48,7 +48,6 @@ public class PanelItem {
meta.addItemFlags(ItemFlag.HIDE_DESTROYS);
meta.addItemFlags(ItemFlag.HIDE_PLACED_ON);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
meta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS);
icon.setItemMeta(meta);
}
@ -89,7 +88,6 @@ public class PanelItem {
this.name = name;
if (meta != null) {
meta.setDisplayName(name);
meta.setLocalizedName(name); //Localized name cannot be overridden by the player using an anvils
icon.setItemMeta(meta);
}
}
@ -135,9 +133,9 @@ public class PanelItem {
}
if (meta != null) {
if (glow) {
meta.addEnchant(Enchantment.ARROW_DAMAGE, 0, glow);
meta.addEnchant(Enchantment.LURE, 0, glow);
} else {
meta.removeEnchant(Enchantment.ARROW_DAMAGE);
meta.removeEnchant(Enchantment.LURE);
}
icon.setItemMeta(meta);

View File

@ -74,6 +74,7 @@ public class TabbedPanel extends Panel implements PanelListener {
* @param page - the page of the tab to show (if multi paged)
*/
public void openPanel(int activeTab, int page) {
if (!tpb.getTabs().containsKey(activeTab)) {
// Request to open a non-existent tab
throw new InvalidParameterException("Attempt to open a non-existent tab in a tabbed panel. Missing tab #" + activeTab);
@ -88,21 +89,17 @@ public class TabbedPanel extends Panel implements PanelListener {
TreeMap<Integer, PanelItem> items = new TreeMap<>();
// Get the tab
Tab tab = tpb.getTabs().get(activeTab);
// Remove any tabs that have no items, if required
if (tpb.isHideIfEmpty()) {
tpb.getTabs().values().removeIf(t -> !t.equals(tab) && t.getPanelItems().stream().noneMatch(Objects::nonNull));
}
// Set up the tabbed header
setupHeader(tab, items);
// Show the active tab
if (tpb.getTabs().containsKey(activeTab)) {
List<PanelItem> panelItems = tab.getPanelItems();
// Adds the flag items
panelItems.stream().filter(Objects::nonNull).skip(page * ITEMS_PER_PAGE).limit(page * ITEMS_PER_PAGE + ITEMS_PER_PAGE).forEach(i -> items.put(items.lastKey() + 1, i));
// set up the footer
setupFooter(items);
// Add forward and backward icons
@ -182,6 +179,7 @@ public class TabbedPanel extends Panel implements PanelListener {
// Reset the closed flag
closed = false;
}
}
/**

View File

@ -14,6 +14,7 @@ import java.util.UUID;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
@ -33,6 +34,8 @@ import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.base.Enums;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.events.OfflineMessageEvent;
@ -62,19 +65,23 @@ public class User implements MetaDataAble {
private static final Map<Particle, Class<?>> VALIDATION_CHECK;
static {
Map<Particle, Class<?>> v = new EnumMap<>(Particle.class);
v.put(Particle.REDSTONE, Particle.DustOptions.class);
v.put(Particle.ITEM_CRACK, ItemStack.class);
v.put(Particle.BLOCK_CRACK, BlockData.class);
v.put(Particle.BLOCK_DUST, BlockData.class);
v.put(Enums.getIfPresent(Particle.class, "DUST")
.or(Enums.getIfPresent(Particle.class, "REDSTONE").or(Particle.FLAME)), Particle.DustOptions.class);
if (Enums.getIfPresent(Particle.class, "ITEM").isPresent()) {
// 1.20.6 Particles
v.put(Particle.ITEM, ItemStack.class);
v.put(Particle.ITEM_COBWEB, ItemStack.class);
v.put(Particle.BLOCK, BlockData.class);
v.put(Particle.DUST_PILLAR, BlockData.class);
v.put(Particle.ENTITY_EFFECT, Color.class);
}
v.put(Particle.FALLING_DUST, BlockData.class);
v.put(Particle.BLOCK_MARKER, BlockData.class);
v.put(Particle.DUST_COLOR_TRANSITION, DustTransition.class);
v.put(Particle.VIBRATION, Vibration.class);
v.put(Particle.SCULK_CHARGE, Float.class);
v.put(Particle.SHRIEK, Integer.class);
v.put(Particle.LEGACY_BLOCK_CRACK, BlockData.class);
v.put(Particle.LEGACY_BLOCK_DUST, BlockData.class);
v.put(Particle.LEGACY_FALLING_DUST, BlockData.class);
VALIDATION_CHECK = Collections.unmodifiableMap(v);
}
@ -711,8 +718,7 @@ public class User implements MetaDataAble {
* server's view distance.
*
* @param particle Particle to display.
* @param dustOptions Particle.DustOptions for the particle to display. Cannot
* be null when particle is {@link Particle#REDSTONE}.
* @param dustOptions Particle.DustOptions for the particle to display.
* @param x X coordinate of the particle to display.
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.
@ -730,7 +736,8 @@ public class User implements MetaDataAble {
// Check if this particle is beyond the viewing distance of the server
if (this.player != null && this.player.getLocation().toVector().distanceSquared(new Vector(x, y,
z)) < (Bukkit.getServer().getViewDistance() * 256 * Bukkit.getServer().getViewDistance())) {
if (particle.equals(Particle.REDSTONE)) {
if (particle.equals(Enums.getIfPresent(Particle.class, "DUST")
.or(Enums.getIfPresent(Particle.class, "REDSTONE").or(Particle.FLAME)))) {
player.spawnParticle(particle, x, y, z, 1, 0, 0, 0, 1, dustOptions);
} else if (dustOptions != null) {
player.spawnParticle(particle, x, y, z, 1, dustOptions);
@ -747,8 +754,7 @@ public class User implements MetaDataAble {
* server's view distance. Compatibility method for older usages.
*
* @param particle Particle to display.
* @param dustOptions Particle.DustOptions for the particle to display. Cannot
* be null when particle is {@link Particle#REDSTONE}.
* @param dustOptions Particle.DustOptions for the particle to display.
* @param x X coordinate of the particle to display.
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.
@ -762,8 +768,7 @@ public class User implements MetaDataAble {
* server's view distance.
*
* @param particle Particle to display.
* @param dustOptions Particle.DustOptions for the particle to display. Cannot
* be null when particle is {@link Particle#REDSTONE}.
* @param dustOptions Particle.DustOptions for the particle to display.
* @param x X coordinate of the particle to display.
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.

View File

@ -119,6 +119,9 @@ public class BlueprintPaster {
Vector off = bp.getBedrock() != null ? bp.getBedrock() : new Vector(0,0,0);
// Calculate location for pasting
this.location = island.getProtectionCenter().toVector().subtract(off).toLocation(world);
// Ensure the y coordinate is within the world limits
int y = Math.min(world.getMaxHeight() - 1, Math.max(world.getMinHeight(), location.getBlockY()));
location.setY(y);
}
private record Bits(Map<Vector, BlueprintBlock> blocks,

View File

@ -61,6 +61,11 @@ public class BlueprintBundle implements DataObject {
@Expose
private int slot = 0;
/**
* Number of times this bundle can be used by a single player. 0 = unlimited
*/
@Expose
private int times = 0;
/**
@ -188,4 +193,18 @@ public class BlueprintBundle implements DataObject {
this.slot = slot;
}
/**
* @return the times
*/
public int getTimes() {
return times;
}
/**
* @param times the times to set
*/
public void setTimes(int times) {
this.times = times;
}
}

View File

@ -286,6 +286,9 @@ public class BlueprintEntity {
* @return the mythicMobsRecord
*/
public MythicMobRecord getMythicMobsRecord() {
if (this.MMtype == null || this.MMLevel == null || this.MMpower == null || this.MMStance == null) {
return null;
}
return new MythicMobRecord(this.MMtype, this.getCustomName(), this.MMLevel, this.MMpower, this.MMStance);
}

View File

@ -13,6 +13,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
@ -41,6 +42,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.adapters.Adapter;
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.bentobox.util.Util;
@ -56,7 +58,7 @@ import world.bentobox.bentobox.util.Util;
public class Island implements DataObject, MetaDataAble {
@Expose
private boolean primary;
private Set<UUID> primaries = new HashSet<>();
/**
* Set to true if this data object has been changed since being loaded from the
@ -243,7 +245,6 @@ public class Island implements DataObject, MetaDataAble {
range = BentoBox.getInstance().getIWM().getIslandDistance(world);
this.protectionRange = protectionRange;
this.maxEverProtectionRange = protectionRange;
this.setChanged();
}
/**
@ -290,6 +291,7 @@ public class Island implements DataObject, MetaDataAble {
this.updatedDate = island.getUpdatedDate();
this.world = island.getWorld();
this.bonusRanges.addAll(island.getBonusRanges());
this.primaries.addAll(island.getPrimaries());
this.setChanged();
}
@ -304,8 +306,10 @@ public class Island implements DataObject, MetaDataAble {
* @param playerUUID - the player's UUID
*/
public void addMember(@NonNull UUID playerUUID) {
setRank(playerUUID, RanksManager.MEMBER_RANK);
setChanged();
if (getRank(playerUUID) != RanksManager.MEMBER_RANK) {
setRank(playerUUID, RanksManager.MEMBER_RANK);
setChanged();
}
}
/**
@ -320,9 +324,12 @@ public class Island implements DataObject, MetaDataAble {
* @return {@code true}
*/
public boolean ban(@NonNull UUID issuer, @NonNull UUID target) {
setRank(target, RanksManager.BANNED_RANK);
log(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString()).build());
setChanged();
if (getRank(target) != RanksManager.BANNED_RANK) {
setRank(target, RanksManager.BANNED_RANK);
log(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString())
.build());
setChanged();
}
return true;
}
@ -1005,25 +1012,30 @@ public class Island implements DataObject, MetaDataAble {
* @param playerUUID - uuid of player
*/
public void removeMember(UUID playerUUID) {
members.remove(playerUUID);
setChanged();
if (members.remove(playerUUID) != null) {
setChanged();
}
}
/**
* @param center the center to set
*/
public void setCenter(@NonNull Location center) {
this.world = center.getWorld();
this.center = center;
setChanged();
if (this.center == null || !center.getWorld().equals(this.center.getWorld()) || !center.equals(this.center)) {
this.world = center.getWorld();
this.center = center;
setChanged();
}
}
/**
* @param createdDate - the createdDate to sets
*/
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
setChanged();
if (this.createdDate != createdDate) {
this.createdDate = createdDate;
setChanged();
}
}
/**
@ -1038,21 +1050,22 @@ public class Island implements DataObject, MetaDataAble {
}
/**
* Set the Island Guard flag rank Also specify whether subflags are affected by
* this method call
* Set the Island Guard flag rank and set any subflags
*
* @param flag - flag
* @param value - Use RanksManager settings, e.g. RanksManager.MEMBER
* @param doSubflags - whether to set subflags
*/
public void setFlag(Flag flag, int value, boolean doSubflags) {
flags.put(flag.getID(), value);
if (flags.containsKey(flag.getID()) && flags.get(flag.getID()) != value) {
flags.put(flag.getID(), value);
setChanged();
}
// Subflag support
if (doSubflags && flag.hasSubflags()) {
// Ensure that a subflag isn't a subflag of itself or else we're in trouble!
flag.getSubflags().forEach(subflag -> setFlag(subflag, value, true));
}
setChanged();
}
/**
@ -1078,7 +1091,6 @@ public class Island implements DataObject, MetaDataAble {
.forEach(f -> result.put(f.getID(),
plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank())));
this.setFlags(result);
setChanged();
}
/**
@ -1097,8 +1109,10 @@ public class Island implements DataObject, MetaDataAble {
* @param name The display name to set.
*/
public void setName(String name) {
this.name = (name != null && !name.equals("")) ? name : null;
setChanged();
if (name == null || !name.equals(this.name)) {
this.name = (name != null && !name.equals("")) ? name : null;
setChanged();
}
}
/**
@ -1130,9 +1144,11 @@ public class Island implements DataObject, MetaDataAble {
* @param protectionRange the protectionRange to set
*/
public void setProtectionRange(int protectionRange) {
this.protectionRange = protectionRange;
this.updateMaxEverProtectionRange();
setChanged();
if (this.protectionRange != protectionRange) {
this.protectionRange = protectionRange;
this.updateMaxEverProtectionRange();
setChanged();
}
}
/**
@ -1164,8 +1180,10 @@ public class Island implements DataObject, MetaDataAble {
* @param purgeProtected - if the island is protected from the Purge
*/
public void setPurgeProtected(boolean purgeProtected) {
this.purgeProtected = purgeProtected;
setChanged();
if (this.purgeProtected != purgeProtected) {
this.purgeProtected = purgeProtected;
setChanged();
}
}
/**
@ -1179,8 +1197,10 @@ public class Island implements DataObject, MetaDataAble {
* @see #setProtectionRange(int)
*/
public void setRange(int range) {
this.range = range;
setChanged();
if (this.range != range) {
this.range = range;
setChanged();
}
}
/**
@ -1191,7 +1211,6 @@ public class Island implements DataObject, MetaDataAble {
*/
public void setRank(User user, int rank) {
setRank(user.getUniqueId(), rank);
setChanged();
}
/**
@ -1199,17 +1218,36 @@ public class Island implements DataObject, MetaDataAble {
* the {@link world.bentobox.bentobox.api.events.island.IslandRankChangeEvent}.
*
* @param uuid UUID of the player
* @param rank rank value
* @param newRank rank value
* @since 1.1
*/
public void setRank(@Nullable UUID uuid, int rank) {
public void setRank(@Nullable UUID uuid, int newRank) {
// Early return if the UUID is null, to avoid unnecessary processing.
if (uuid == null) {
return; // Defensive code
return;
}
// Use an AtomicBoolean to track if the member's rank has been changed.
AtomicBoolean isRankChanged = new AtomicBoolean(false);
// Attempt to update the member's rank, if necessary.
members.compute(uuid, (key, existingRank) -> {
// If the member does not exist or their rank is different, update the rank.
if (existingRank == null || existingRank != newRank) {
isRankChanged.set(true);
return newRank; // Update the rank.
}
// No change needed; return the existing rank.
return existingRank;
});
// If the rank was changed, notify the change and log the update.
if (isRankChanged.get()) {
setChanged(); // Notify that a change has occurred.
}
members.put(uuid, rank);
setChanged();
}
/**
* @param ranks the ranks to set
*/
@ -1266,7 +1304,6 @@ public class Island implements DataObject, MetaDataAble {
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
setChanged();
}
/**
@ -1274,7 +1311,6 @@ public class Island implements DataObject, MetaDataAble {
*/
public void setUpdatedDate(long updatedDate) {
this.updatedDate = updatedDate;
setChanged();
}
/**
@ -1347,8 +1383,13 @@ public class Island implements DataObject, MetaDataAble {
* @param l - location
*/
public void setSpawnPoint(Environment islandType, Location l) {
spawnPoint.put(islandType, l);
setChanged();
spawnPoint.compute(islandType, (key, value) -> {
if (value == null || !value.equals(l)) {
setChanged(); // Call setChanged only if the value is updated.
return l;
}
return value;
});
}
/**
@ -1368,8 +1409,9 @@ public class Island implements DataObject, MetaDataAble {
* @param rank rank value
*/
public void removeRank(Integer rank) {
members.values().removeIf(rank::equals);
setChanged();
if (members.values().removeIf(rank::equals)) {
setChanged();
}
}
/**
@ -1455,7 +1497,6 @@ public class Island implements DataObject, MetaDataAble {
*/
public void setGameMode(String gameMode) {
this.gameMode = gameMode;
setChanged();
}
/**
@ -1518,8 +1559,9 @@ public class Island implements DataObject, MetaDataAble {
if (cooldowns.containsKey(flag.getID()) && cooldowns.get(flag.getID()) > System.currentTimeMillis()) {
return true;
}
cooldowns.remove(flag.getID());
setChanged();
if (cooldowns.remove(flag.getID()) != null) {
setChanged();
}
return false;
}
@ -1603,8 +1645,13 @@ public class Island implements DataObject, MetaDataAble {
public void setRankCommand(String command, int rank) {
if (this.commandRanks == null)
this.commandRanks = new HashMap<>();
this.commandRanks.put(command, rank);
setChanged();
commandRanks.compute(command, (key, value) -> {
if (value == null || !value.equals(rank)) {
setChanged(); // Call setChanged only if the value is updated.
return rank;
}
return value;
});
}
/**
@ -1624,8 +1671,10 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.6.0
*/
public void setReserved(boolean reserved) {
this.reserved = reserved;
setChanged();
if (this.reserved != reserved) {
this.reserved = reserved;
setChanged();
}
}
/**
@ -1658,17 +1707,19 @@ public class Island implements DataObject, MetaDataAble {
}
/**
* Indicates the fields have been changed. Used to optimize saving on shutdown.
* Indicates the fields have been changed. Used to optimize saving on shutdown and notify other servers
*/
public void setChanged() {
this.setUpdatedDate(System.currentTimeMillis());
this.changed = true;
IslandsManager.updateIsland(this);
}
/**
* @param changed the changed to set
* Resets the changed if the island has been saved
*/
public void setChanged(boolean changed) {
this.changed = changed;
public void clearChanged() {
this.changed = false;
}
/**
@ -1692,6 +1743,9 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setProtectionCenter(Location location) throws IOException {
if (this.location.equals(location)) {
return; // nothing to do
}
if (!this.inIslandSpace(location)) {
throw new IOException("Location must be in island space");
}
@ -1741,6 +1795,9 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void addHome(String name, Location location) {
if (getHomes().containsKey(name) && getHomes().get(name).equals(location)) {
return; // nothing to do
}
if (location != null) {
Vector v = location.toVector();
if (!this.getBoundingBox().contains(v)) {
@ -1763,8 +1820,11 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public boolean removeHome(String name) {
setChanged();
return getHomes().remove(name.toLowerCase()) != null;
if (getHomes().remove(name.toLowerCase()) != null) {
setChanged();
return true;
}
return false;
}
/**
@ -1774,8 +1834,11 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.20.0
*/
public boolean removeHomes() {
setChanged();
return getHomes().keySet().removeIf(k -> !k.isEmpty());
if (getHomes().keySet().removeIf(k -> !k.isEmpty())) {
setChanged();
return true;
}
return false;
}
/**
@ -1814,8 +1877,10 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setMaxHomes(@Nullable Integer maxHomes) {
this.maxHomes = maxHomes;
setChanged();
if (this.maxHomes != maxHomes) {
this.maxHomes = maxHomes;
setChanged();
}
}
/**
@ -1834,8 +1899,10 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setMaxMembers(Map<Integer, Integer> maxMembers) {
this.maxMembers = maxMembers;
setChanged();
if (this.maxMembers != maxMembers) {
this.maxMembers = maxMembers;
setChanged();
}
}
/**
@ -1860,7 +1927,13 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setMaxMembers(int rank, Integer maxMembers) {
getMaxMembers().put(rank, maxMembers);
getMaxMembers().compute(rank, (key, value) -> {
if (value == null || !value.equals(maxMembers)) {
setChanged(); // Call setChanged only if the value is updated.
return maxMembers;
}
return value;
});
}
/**
@ -1923,8 +1996,9 @@ public class Island implements DataObject, MetaDataAble {
* @param id id to identify this bonus
*/
public void clearBonusRange(String id) {
this.getBonusRanges().removeIf(r -> r.getUniqueId().equals(id));
setChanged();
if (this.getBonusRanges().removeIf(r -> r.getUniqueId().equals(id))) {
setChanged();
}
}
/**
@ -1936,18 +2010,50 @@ public class Island implements DataObject, MetaDataAble {
}
/**
* @param userID user UUID
* @return the primary
*/
public boolean isPrimary() {
return primary;
public boolean isPrimary(UUID userID) {
return getPrimaries().contains(userID);
}
/**
* @param primary the primary to set
* Set this island to be the primary for this user
* @param userID user UUID
*/
public void setPrimary(boolean primary) {
this.primary = primary;
setChanged();
public void setPrimary(UUID userID) {
if (getPrimaries().add(userID)) {
setChanged();
}
}
/**
* Remove the primary island
* @param userID user UUID
*/
public void removePrimary(UUID userID) {
if (getPrimaries().remove(userID)) {
setChanged();
}
}
/**
* Check if a player is in this island's team
* @param playerUUID player's UUID
* @return true if in team
* @since 2.3.0
*/
public boolean inTeam(UUID playerUUID) {
return this.getMemberSet().contains(playerUUID);
}
/**
* Check if this island has a team
* @return true if this island has a team
* @since 2.3.0
*/
public boolean hasTeam() {
return this.getMemberSet().size() > 1;
}
/*
@ -1967,4 +2073,41 @@ public class Island implements DataObject, MetaDataAble {
+ commandRanks + ", reserved=" + reserved + ", metaData=" + metaData + ", homes=" + homes
+ ", maxHomes=" + maxHomes + "]";
}
/**
* @return the primaries
*/
public Set<UUID> getPrimaries() {
if (primaries == null) {
primaries = new HashSet<>();
}
return primaries;
}
/**
* @param primaries the primaries to set
*/
public void setPrimaries(Set<UUID> primaries) {
this.primaries = primaries;
setChanged();
}
@Override
public int hashCode() {
return Objects.hash(uniqueId);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Island other = (Island) obj;
return Objects.equals(uniqueId, other.uniqueId);
}
}

View File

@ -6,13 +6,10 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.Expose;
@ -30,8 +27,6 @@ import world.bentobox.bentobox.util.Util;
*/
@Table(name = "Players")
public class Players implements DataObject, MetaDataAble {
@Expose
private Map<Location, Integer> homeLocations = new HashMap<>();
@Expose
private String uniqueId;
@Expose
@ -77,7 +72,6 @@ public class Players implements DataObject, MetaDataAble {
*/
public Players(BentoBox plugin, UUID uniqueId) {
this.uniqueId = uniqueId.toString();
homeLocations = new HashMap<>();
locale = "";
// Try to get player's name
this.playerName = Bukkit.getOfflinePlayer(uniqueId).getName();
@ -86,72 +80,6 @@ public class Players implements DataObject, MetaDataAble {
}
}
/**
* Gets the default home location.
* @param world - world to check
* @return Location - home location in world
* @deprecated Homes are stored in the Island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
@Nullable
public Location getHomeLocation(World world) {
return getHomeLocation(world, 1); // Default
}
/**
* Gets the home location by number for world
* @param world - includes world and any related nether or end worlds
* @param number - a number
* @return Location of this home or null if not available
* @deprecated Homes are stored in the island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
@Nullable
public Location getHomeLocation(World world, int number) {
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
return homeLocations.entrySet().stream()
.filter(en -> Util.sameWorld(en.getKey().getWorld(), world) && en.getValue() == number)
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
}
/**
* @param world - world
* @return Map of home locations
* @deprecated Homes are stored in the island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
public Map<Location, Integer> getHomeLocations(World world) {
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
return homeLocations.entrySet().stream().filter(e -> Util.sameWorld(e.getKey().getWorld(),world))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
* @return the homeLocations
* @deprecated Homes are stored in the Island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
public Map<Location, Integer> getHomeLocations() {
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
return homeLocations;
}
/**
* @param homeLocations the homeLocations to set
* @deprecated Homes are stored in the Island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void setHomeLocations(Map<Location, Integer> homeLocations) {
this.homeLocations = homeLocations;
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
}
/**
* @param playerName the playerName to set
*/
@ -202,30 +130,6 @@ public class Players implements DataObject, MetaDataAble {
this.resets.put(world.getName(), resets);
}
/**
* Stores the home location of the player in a String format
*
* @param l a Bukkit location
* @deprecated Home locations are stored in islands
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void setHomeLocation(final Location l) {
setHomeLocation(l, 1);
}
/**
* Stores the numbered home location of the player. Numbering starts at 1.
* @param location - the location
* @param number - a number
* @deprecated Home locations are no longer stored for players. They are stored in islands.
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void setHomeLocation(Location location, int number) {
// Remove any home locations in the same world with the same number
homeLocations.entrySet().removeIf(e -> e.getKey() == null || (Util.sameWorld(location.getWorld(), e.getKey().getWorld()) && e.getValue().equals(number)));
homeLocations.put(location, number);
}
/**
* Set the uuid for this player object
* @param uuid - UUID
@ -234,16 +138,6 @@ public class Players implements DataObject, MetaDataAble {
uniqueId = uuid.toString();
}
/**
* Clears all home Locations in world
* @param world - world
* @deprecated Home locations are no longer stored for players. Use {@link world.bentobox.bentobox.managers.IslandsManager}
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void clearHomeLocations(World world) {
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null || Util.sameWorld(l.getWorld(), world));
}
/**
* @return the locale
*/
@ -350,24 +244,6 @@ public class Players implements DataObject, MetaDataAble {
}
}
/**
* Returns the display mode for the Flags in the Settings Panel.
* @return the display mode for the Flags in the Settings Panel.
* @since 1.6.0
*/
public Flag.Mode getFlagsDisplayMode() {
return flagsDisplayMode;
}
/**
* Sets the display mode for the Flags in the Settings Panel.
* @param flagsDisplayMode the display mode for the Flags in the Settings Panel.
* @since 1.6.0
*/
public void setFlagsDisplayMode(Flag.Mode flagsDisplayMode) {
this.flagsDisplayMode = flagsDisplayMode;
}
/**
* @return the metaData
* @since 1.15.5

View File

@ -0,0 +1,112 @@
package world.bentobox.bentobox.database.objects;
import java.util.Objects;
import java.util.UUID;
import com.google.gson.annotations.Expose;
/**
* Data object for team invites
*/
@Table(name = "TeamInvites")
public class TeamInvite implements DataObject {
/**
* Type of invitation
*
*/
public enum Type {
COOP,
TEAM,
TRUST
}
@Expose
private Type type;
@Expose
private UUID inviter;
@Expose
private String islandID;
@Expose
private String uniqueId;
/**
* @param type - invitation type, e.g., coop, team, trust
* @param inviter - UUID of inviter
* @param invitee - UUID of invitee
* @param islandID - the unique ID of the island this invite is for
*/
public TeamInvite(Type type, UUID inviter, UUID invitee, String islandID) {
this.type = type;
this.uniqueId = invitee.toString();
this.inviter = inviter;
this.islandID = islandID;
}
@Override
public String getUniqueId() {
// Inviter
return this.uniqueId;
}
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
/**
* @return the type
*/
public Type getType() {
return type;
}
/**
* @return the invitee
*/
public UUID getInvitee() {
return UUID.fromString(uniqueId);
}
/**
* @return the inviter
*/
public UUID getInviter() {
return inviter;
}
/**
* @return the islandID
*/
public String getIslandID() {
return islandID;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(inviter, uniqueId, type);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TeamInvite other)) {
return false;
}
return Objects.equals(inviter, other.inviter) && Objects.equals(uniqueId, other.getUniqueId())
&& type == other.type;
}
}

View File

@ -1,6 +1,11 @@
package world.bentobox.bentobox.hooks;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
@ -8,6 +13,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.eclipse.jdt.annotation.Nullable;
import dev.lone.itemsadder.api.CustomBlock;
import world.bentobox.bentobox.BentoBox;
@ -22,6 +28,19 @@ import world.bentobox.bentobox.managers.RanksManager;
* Hook to enable itemsadder blocks to be deleted when islands are deleted.
* It also includes a flag to track explosion access
*/
/*
* add some methods under CustomBlock#Advanced class.
public static void deleteAllCustomBlocksInChunk(Chunk chunk)
@Nullable
public List<Location> getAllBlocksLocationsList(Chunk chunk)
@Nullable
public Map<String, Location> getAllBlocksLocations(Chunk chunk)
public void runActionOnBlocks(Chunk chunk, BiConsumer<String, Location> action)
*/
public class ItemsAdderHook extends Hook {
/**
@ -78,6 +97,24 @@ public class ItemsAdderHook extends Hook {
// CustomBlock.remove(location);
}
public static void deleteAllCustomBlocksInChunk(Chunk chunk) {
CustomBlock.Advanced.deleteAllCustomBlocksInChunk(chunk);
}
@Nullable
public List<Location> getAllBlocksLocationsList(Chunk chunk) {
return CustomBlock.Advanced.getAllBlocksLocationsList(chunk);
}
@Nullable
public Map<Location, String> getAllBlocksLocations(Chunk chunk) {
return CustomBlock.Advanced.getAllBlocksLocations(chunk);
}
public void runActionOnBlocks(Chunk chunk, BiConsumer<String, Location> action) {
CustomBlock.Advanced.runActionOnBlocks(chunk, action);
}
class BlockInteractListener extends FlagListener {
/**

View File

@ -1,8 +1,10 @@
package world.bentobox.bentobox.hooks;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.bukkit.DyeColor;
import org.bukkit.Material;
@ -22,6 +24,7 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.jetbrains.annotations.Nullable;
import com.google.common.base.Enums;
import com.meowj.langutils.lang.LanguageHelper;
import world.bentobox.bentobox.BentoBox;
@ -264,29 +267,48 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getPotionName(potionType, getUserLocale(user));
}
return generalPotionName(potionType);
}
private static String generalPotionName(PotionType potionType) {
return switch (potionType) {
case UNCRAFTABLE -> "Uncraftable Potion";
case WATER -> "Water Bottle";
case MUNDANE -> "Mundane Potion";
case THICK -> "Thick Potion";
case AWKWARD -> "Awkward Potion";
case NIGHT_VISION -> "Potion of Night Vision";
case INVISIBILITY -> "Potion of Invisibility";
case JUMP -> "Potion of Leaping";
case FIRE_RESISTANCE -> "Potion of Fire Resistance";
case SPEED -> "Potion of Swiftness";
case SLOWNESS -> "Potion of Slowness";
case WATER_BREATHING -> "Potion of Water Breathing";
case INSTANT_HEAL -> "Potion of Healing";
case INSTANT_DAMAGE -> "Potion of Harming";
case POISON -> "Potion of Poison";
case REGEN -> "Potion of Regeneration";
case STRENGTH -> "Potion of Strength";
case WEAKNESS -> "Potion of Weakness";
case LUCK -> "Potion of Luck";
case TURTLE_MASTER -> "Potion of the Turtle Master";
case SLOW_FALLING -> "Potion of Slow Falling";
default -> "Unknown Potion";
case LONG_FIRE_RESISTANCE -> "Potion of Long Fire Resistance";
case LONG_INVISIBILITY -> "Potion of Long Invisibility";
case LONG_NIGHT_VISION -> "Potion of Long Night Vision";
case LONG_POISON -> "Potion of Long Poison";
case LONG_REGENERATION -> "Potion of Long Regeneration";
case LONG_SLOWNESS -> "Potion of Long Slowness";
case LONG_SLOW_FALLING -> "Potion of Long Slow Falling";
case LONG_STRENGTH -> "Potion of Long Strength";
case LONG_SWIFTNESS -> "Potion of Long Swiftness";
case LONG_TURTLE_MASTER -> "Potion of Long Turtle Master";
case LONG_WATER_BREATHING -> "Potion of Long Water Breathing";
case LONG_WEAKNESS -> "Potion of Long Weakness";
case STRONG_HARMING -> "Potion of Strong Harming";
case STRONG_HEALING -> "Potion of Strong Healing";
case STRONG_LEAPING -> "Potion of Strong Leaping";
case STRONG_POISON -> "Potion of Strong Poison";
case STRONG_REGENERATION -> "Potion of Strong Regeneration";
case STRONG_SLOWNESS -> "Potion of Strong Slowness";
case STRONG_STRENGTH -> "Potion of Strong Strength";
case STRONG_SWIFTNESS -> "Potion of Swiftness";
case STRONG_TURTLE_MASTER -> "Potion of Strong Turtle Master";
default -> "Potion of " + Util.prettifyText(potionType.name());
};
}
@ -302,30 +324,7 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getSplashPotionName(potionType, getUserLocale(user));
}
return switch (potionType) {
case UNCRAFTABLE -> "Splash Uncraftable Potion";
case WATER -> "Splash Water Bottle";
case MUNDANE -> "Mundane Splash Potion";
case THICK -> "Thick Splash Potion";
case AWKWARD -> "Awkward Splash Potion";
case NIGHT_VISION -> "Splash Potion of Night Vision";
case INVISIBILITY -> "Splash Potion of Invisibility";
case JUMP -> "Splash Potion of Leaping";
case FIRE_RESISTANCE -> "Splash Potion of Fire Resistance";
case SPEED -> "Splash Potion of Swiftness";
case SLOWNESS -> "Splash Potion of Slowness";
case WATER_BREATHING -> "Splash Potion of Water Breathing";
case INSTANT_HEAL -> "Splash Potion of Healing";
case INSTANT_DAMAGE -> "Splash Potion of Harming";
case POISON -> "Splash Potion of Poison";
case REGEN -> "Splash Potion of Regeneration";
case STRENGTH -> "Splash Potion of Strength";
case WEAKNESS -> "Splash Potion of Weakness";
case LUCK -> "Splash Potion of Luck";
case TURTLE_MASTER -> "Splash Potion of the Turtle Master";
case SLOW_FALLING -> "Splash Potion of Slow Falling";
default -> "Unknown Splash Potion";
};
return "Splash" + generalPotionName(potionType);
}
/**
@ -339,30 +338,7 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getLingeringPotionName(potionType, getUserLocale(user));
}
return switch (potionType) {
case UNCRAFTABLE -> "Lingering Uncraftable Potion";
case WATER -> "Lingering Water Bottle";
case MUNDANE -> "Mundane Lingering Potion";
case THICK -> "Thick Lingering Potion";
case AWKWARD -> "Awkward Lingering Potion";
case NIGHT_VISION -> "Lingering Potion of Night Vision";
case INVISIBILITY -> "Lingering Potion of Invisibility";
case JUMP -> "Lingering Potion of Leaping";
case FIRE_RESISTANCE -> "Lingering Potion of Fire Resistance";
case SPEED -> "Lingering Potion of Swiftness";
case SLOWNESS -> "Lingering Potion of Slowness";
case WATER_BREATHING -> "Lingering Potion of Water Breathing";
case INSTANT_HEAL -> "Lingering Potion of Healing";
case INSTANT_DAMAGE -> "Lingering Potion of Harming";
case POISON -> "Lingering Potion of Poison";
case REGEN -> "Lingering Potion of Regeneration";
case STRENGTH -> "Lingering Potion of Strength";
case WEAKNESS -> "Lingering Potion of Weakness";
case LUCK -> "Lingering Potion of Luck";
case TURTLE_MASTER -> "Lingering Potion of the Turtle Master";
case SLOW_FALLING -> "Lingering Potion of Slow Falling";
default -> "Unknown Lingering Potion";
};
return "Lingering" + generalPotionName(potionType);
}
/**
@ -376,28 +352,7 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getTippedArrowName(potionType, getUserLocale(user));
}
return switch (potionType) {
case UNCRAFTABLE -> "Uncraftable Tipped Arrow";
case WATER -> "Arrow of Splashing";
case MUNDANE, THICK, AWKWARD -> "Tipped Arrow";
case NIGHT_VISION -> "Arrow of Night Vision";
case INVISIBILITY -> "Arrow of Invisibility";
case JUMP -> "Arrow of Leaping";
case FIRE_RESISTANCE -> "Arrow of Fire Resistance";
case SPEED -> "Arrow of Swiftness";
case SLOWNESS -> "Arrow of Slowness";
case WATER_BREATHING -> "Arrow of Water Breathing";
case INSTANT_HEAL -> "Arrow of Healing";
case INSTANT_DAMAGE -> "Arrow of Harming";
case POISON -> "Arrow of Poison";
case REGEN -> "Arrow of Regeneration";
case STRENGTH -> "Arrow of Strength";
case WEAKNESS -> "Arrow of Weakness";
case LUCK -> "Arrow of Luck";
case TURTLE_MASTER -> "Arrow of the Turtle Master";
case SLOW_FALLING -> "Arrow of Slow Falling";
default -> "Unknown Arrow";
};
return generalPotionName(potionType).replaceAll("Potion", "Arrow");
}
/**
@ -413,11 +368,12 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getPotionBaseEffectName(potionType, getUserLocale(user));
}
PotionEffectType effectType = potionType.getEffectType();
if (effectType == null) {
List<PotionEffect> effects = potionType.getPotionEffects();
if (effects.isEmpty()) {
return "No Effects";
}
return Util.prettifyText(effectType.getName());
return effects.stream().map(effect -> Util.prettifyText(effect.getType().getKey().getKey()))
.collect(Collectors.joining(", "));
}
/**
@ -430,7 +386,7 @@ public class LangUtilsHook extends Hook {
public static String getPotionEffectName(PotionEffectType effectType, User user) {
return hooked
? LanguageHelper.getPotionEffectName(effectType, getUserLocale(user))
: Util.prettifyText(effectType.getName());
: Util.prettifyText(effectType.getKey().getKey());
}
/**

View File

@ -0,0 +1,420 @@
package world.bentobox.bentobox.hooks;
import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.github.puregero.multilib.DataStorageImpl;
import com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.api.hooks.Hook;
/**
* Hook for Multipaper
*/
public class MultipaperHook extends Hook {
public MultipaperHook() {
super("multipaper", Material.PAPER);
}
@Override
public boolean hook() {
return MultiLib.isMultiPaper();
}
/**
* @return true if this is a Multipaper server
*/
@Override
public boolean isPluginAvailable() {
return MultiLib.isMultiPaper();
}
/**
* Always null because it is not a plugin
* @return null
*/
@Nullable
@Override
public Plugin getPlugin() {
return null;
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(World world, int cx, int cz) {
return MultiLib.isChunkExternal(world, cx, cz);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Location location) {
return MultiLib.isChunkExternal(location);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Entity entity) {
return MultiLib.isChunkExternal(entity);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Block block) {
return MultiLib.isChunkExternal(block);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Chunk chunk) {
return MultiLib.isChunkExternal(chunk);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(World world, int cx, int cz) {
return MultiLib.isChunkLocal(world, cx, cz);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Location location) {
return MultiLib.isChunkLocal(location);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Entity entity) {
return MultiLib.isChunkLocal(entity);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Block block) {
return MultiLib.isChunkLocal(block);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Chunk chunk) {
return MultiLib.isChunkLocal(chunk);
}
/**
* Returns whether the player is on an external server or not.
*
* @return True if the player is on an external server.
*/
public static boolean isExternalPlayer(Player player) {
return MultiLib.isExternalPlayer(player);
}
/**
* Returns whether the player is on this server or not.
*
* @return True if the player is on this server.
*/
public static boolean isLocalPlayer(Player player) {
return MultiLib.isLocalPlayer(player);
}
/**
* Get the bungeecord name of this server.
*
* @return the bungeecord name of this server
*/
@NonNull
public static String getLocalServerName() {
return MultiLib.getLocalServerName();
}
/**
* Get the bungeecord name of the server that this player is on.
*
* @return The bungeecord name of the server the player is on for external
* players, or null for local players.
*/
@Nullable
public static String getExternalServerName(Player player) {
return MultiLib.getExternalServerName(player);
}
/**
* Returns cross-server data that is stored under the specified key. Note
* that all plugins share the same set of keys. This data is
* non-persistent, it will be lost when the player disconnects.
*
* @param key The key the data is stored under.
* @return The data stored under the key, or null if the key isn't set.
*/
public static String getData(Player player, String key) {
return MultiLib.getData(player, key);
}
/**
* Store cross-server data under the specified key. Note that all plugins
* share the same set of keys. This data is non-persistent, it will be
* lost when the player disconnects.
*
* @param key The key to store the data under.
* @param value The data to store under the key.
*/
public static void setData(Player player, String key, String value) {
MultiLib.setData(player, key, value);
}
/**
* Returns cross-server data that is stored under the specified key. Note
* that all plugins share the same set of keys. This data is persistent,
* it will be saved even if the player disconnects. This persistent data is
* saved onto the player's .dat file.
*
* @param key The key the data is stored under.
* @return The data stored under the key, or null if the key isn't set.
*/
public static String getPersistentData(Player player, String key) {
return MultiLib.getPersistentData(player, key);
}
/**
* Store cross-server data under the specified key. Note that all plugins
* share the same set of keys. This data is persistent, it will be saved
* even if the player disconnects. This persistent data is saved onto the
* player's .dat file.
*
* @param key The key to store the data under.
* @param value The data to store under the key.
*/
public static void setPersistentData(Player player, String key, String value) {
MultiLib.setPersistentData(player, key, value);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callback A handler for any data received
*/
public static void on(Plugin plugin, String channel, Consumer<byte[]> callback) {
MultiLib.on(plugin, channel, callback);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callback A handler for any data received
*/
public static void onString(Plugin plugin, String channel, Consumer<String> callback) {
MultiLib.onString(plugin, channel, callback);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callbackWithReply A handler for any data received, and a method to reply to the server on a specified channel
*/
public static void on(Plugin plugin, String channel,
BiConsumer<byte[], BiConsumer<String, byte[]>> callbackWithReply) {
MultiLib.on(plugin, channel, callbackWithReply);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callbackWithReply A handler for any data received, and a method to reply to the server on a specified channel
*/
public static void onString(Plugin plugin, String channel,
BiConsumer<String, BiConsumer<String, String>> callbackWithReply) {
MultiLib.onString(plugin, channel, callbackWithReply);
}
/**
* Notify all other servers.
*
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(String channel, byte[] data) {
MultiLib.notify(channel, data);
}
/**
* Notify all other servers.
*
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(String channel, String data) {
MultiLib.notify(channel, data);
}
/**
* Notify other servers with the specified chunk loaded
*
* @param chunk The chunk that's loaded
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(Chunk chunk, String channel, byte[] data) {
MultiLib.notify(chunk, channel, data);
}
/**
* Notify other servers with the specified chunk loaded
*
* @param chunk The chunk that's loaded
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(Chunk chunk, String channel, String data) {
MultiLib.notify(chunk, channel, data);
}
/**
* Notify the owning server of the specified chunk.
* This chunk must be loaded on this server.
* This will notify this server if this server is the owning server.
*
* @param chunk The loaded chunk with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Chunk chunk, String channel, byte[] data) {
MultiLib.notifyOwningServer(chunk, channel, data);
}
/**
* Notify the owning server of the specified chunk.
* This chunk must be loaded on this server.
* This will notify this server if this server is the owning server.
*
* @param chunk The loaded chunk with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Chunk chunk, String channel, String data) {
MultiLib.notifyOwningServer(chunk, channel, data);
}
/**
* Notify the owning server of the specified player.
* This will notify this server if this server is the owning server.
*
* @param player The player with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Player player, String channel, byte[] data) {
MultiLib.notifyOwningServer(player, channel, data);
}
/**
* Notify the owning server of the specified player.
* This will notify this server if this server is the owning server.
*
* @param player The player with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Player player, String channel, String data) {
MultiLib.notifyOwningServer(player, channel, data);
}
/**
* Says a message (or runs a command) on other servers excluding this one.
*
* @param message The chat message to say
*/
public static void chatOnOtherServers(Player player, String message) {
MultiLib.chatOnOtherServers(player, message);
}
/**
* Returns all online players across all server instances.
*
* @return a view of all online players
*/
public static Collection<? extends Player> getAllOnlinePlayers() {
return MultiLib.getAllOnlinePlayers();
}
/**
* Returns players logged into your single local server instance.
*
* @return a view of players online on your local instance
*/
public static Collection<? extends Player> getLocalOnlinePlayers() {
return MultiLib.getLocalOnlinePlayers();
}
/**
* Gets the multipaper key-value data storage. Accessing this data is
* asynchronous. This storage medium is hosted on the Master instance,
* or a yaml file when using Bukkit.
*
* @return the multipaper data storage
*/
public static DataStorageImpl getDataStorage() {
return MultiLib.getDataStorage();
}
}

View File

@ -55,6 +55,9 @@ public class MythicMobsHook extends Hook {
* @return true if spawn is successful
*/
public boolean spawnMythicMob(MythicMobRecord mmr, Location spawnLocation) {
if (!this.isPluginAvailable()) {
return false;
}
return MythicBukkit.inst().getMobManager().getMythicMob(mmr.type()).map(mob -> {
// A delay is required before spawning, I assume because the blocks are pasted using NMS
Bukkit.getScheduler().runTaskLater(getPlugin(), () -> {

View File

@ -2,6 +2,7 @@ package world.bentobox.bentobox.listeners;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
@ -61,7 +62,7 @@ public class JoinLeaveListener implements Listener {
// Make sure the player is loaded into the cache or create the player if they
// don't exist
players.addPlayer(playerUUID);
players.getPlayer(playerUUID);
// Reset island resets if required
plugin.getIWM().getOverWorlds().stream()
@ -74,11 +75,14 @@ public class JoinLeaveListener implements Listener {
// Set the player's name (it may have changed), but only if it isn't empty
if (!user.getName().isEmpty()) {
players.setPlayerName(user);
players.save(playerUUID);
} else {
plugin.logWarning("Player that just logged in has no name! " + playerUUID);
}
// Set the primary island to the player's location if this is their island
plugin.getIslands().getIslandAt(user.getLocation()).filter(i -> user.getUniqueId().equals(i.getOwner()))
.ifPresent(i -> plugin.getIslands().setPrimaryIsland(playerUUID, i));
// If mobs have to be removed when a player joins, then wipe all the mobs on his
// island.
if (plugin.getIslands().locationIsOnIsland(event.getPlayer(), user.getLocation())
@ -106,7 +110,7 @@ public class JoinLeaveListener implements Listener {
private void firstTime(User user) {
// Make sure the player is loaded into the cache or create the player if they
// don't exist
players.addPlayer(user.getUniqueId());
players.getPlayer(user.getUniqueId());
plugin.getIWM().getOverWorlds().stream().filter(w -> plugin.getIWM().isCreateIslandOnFirstLoginEnabled(w))
.forEach(w -> {
@ -177,8 +181,10 @@ public class JoinLeaveListener implements Listener {
user.getPlayer().getInventory().clear();
}
playerData.getPendingKicks().remove(world.getName());
players.save(user.getUniqueId());
Set<String> kicks = playerData.getPendingKicks();
kicks.remove(world.getName());
playerData.setPendingKicks(kicks);
}
}
@ -219,7 +225,7 @@ public class JoinLeaveListener implements Listener {
.filter(island -> island.getMembers().containsKey(event.getPlayer().getUniqueId())).forEach(island -> {
// Are there any online players still for this island?
if (Bukkit.getOnlinePlayers().stream().filter(p -> !event.getPlayer().equals(p))
.noneMatch(p -> island.getMemberSet().contains(p.getUniqueId()))) {
.noneMatch(p -> island.inTeam(p.getUniqueId()))) {
// No, there are no more players online on this island
// Tell players they are being removed
island.getMembers().entrySet().stream().filter(e -> e.getValue() == RanksManager.COOP_RANK)
@ -232,7 +238,6 @@ public class JoinLeaveListener implements Listener {
});
// Remove any coop associations from the player logging out
plugin.getIslands().clearRank(RanksManager.COOP_RANK, event.getPlayer().getUniqueId());
players.save(event.getPlayer().getUniqueId());
User.removePlayer(event.getPlayer());
}
}

View File

@ -61,6 +61,7 @@ public class PanelListenerManager implements Listener {
// Refresh
l.refreshPanel();
});
} else {
// Wrong name - delete this panel
openPanels.remove(user.getUniqueId());

View File

@ -0,0 +1,59 @@
package world.bentobox.bentobox.listeners;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
import world.bentobox.bentobox.util.Util;
/**
* Updates chunks in seed worlds if they have been generated in the main world
* @author tastybento
*/
public class SeedWorldMakerListener implements Listener {
private final BentoBox plugin;
/**
* Whether BentoBox is ready or not.
* This helps to avoid hanging out the server on startup as a lot of {@link ChunkLoadEvent} are called at this time.
* @since 1.1
*/
private boolean ready;
public SeedWorldMakerListener(BentoBox bentoBox) {
this.plugin = bentoBox;
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onBentoBoxReady(BentoBoxReadyEvent e) {
ready = true;
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onChunkLoad(ChunkLoadEvent e) {
if (!ready || !e.getChunk().isGenerated()) {
return;
}
World world = e.getWorld();
plugin.getIWM().getAddon(world).filter(GameModeAddon::isUsesNewChunkGeneration).ifPresent(gma -> {
World seed = Bukkit.getWorld(world.getName() + "/bentobox");
int x = e.getChunk().getX();
int z = e.getChunk().getZ();
if (seed != null && !seed.getChunkAt(x, z, false).isGenerated()) {
Util.getChunkAtAsync(seed, x, z, true);
}
});
}
}

View File

@ -60,9 +60,6 @@ public class CommandCycleClick implements ClickHandler {
}
// Apply change to panel
panel.getInventory().setItem(slot, commandRankClickListener.getPanelItem(command, user, world).getItem());
// Save island
plugin.getIslands().save(island);
} else {
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
}

View File

@ -17,6 +17,7 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import com.google.common.base.Enums;
import com.google.common.base.Optional;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
@ -28,7 +29,6 @@ import world.bentobox.bentobox.lists.Flags;
*
*/
public class BreedingListener extends FlagListener {
/**
* A list of items that cause breeding if a player has them in their hand and they click an animal
* This list may need to be extended with future versions of Minecraft.
@ -41,7 +41,10 @@ public class BreedingListener extends FlagListener {
bi.put(EntityType.HORSE, Arrays.asList(Material.GOLDEN_APPLE, Material.GOLDEN_CARROT));
bi.put(EntityType.DONKEY, Arrays.asList(Material.GOLDEN_APPLE, Material.GOLDEN_CARROT));
bi.put(EntityType.COW, Collections.singletonList(Material.WHEAT));
bi.put(EntityType.MUSHROOM_COW, Collections.singletonList(Material.WHEAT));
Optional<EntityType> mc = Enums.getIfPresent(EntityType.class, "MUSHROOM_COW");
if (mc.isPresent()) {
bi.put(mc.get(), Collections.singletonList(Material.WHEAT));
}
bi.put(EntityType.SHEEP, Collections.singletonList(Material.WHEAT));
bi.put(EntityType.PIG, Arrays.asList(Material.CARROT, Material.POTATO, Material.BEETROOT));
bi.put(EntityType.CHICKEN, Arrays.asList(Material.WHEAT_SEEDS, Material.PUMPKIN_SEEDS, Material.MELON_SEEDS, Material.BEETROOT_SEEDS));

View File

@ -9,6 +9,7 @@ import org.bukkit.event.player.PlayerUnleashEntityEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
@ -42,7 +43,8 @@ public class LeashListener extends FlagListener {
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerLeashHitch(final HangingPlaceEvent e) {
if (e.getEntity().getType().equals(EntityType.LEASH_HITCH)) {
EntityType LEASH_HITCH = Util.findFirstMatchingEnum(EntityType.class, "LEASH_HITCH", "LEASH_KNOT");
if (e.getEntity().getType().equals(LEASH_HITCH)) {
checkIsland(e, e.getPlayer(), e.getEntity().getLocation(), Flags.LEASH);
}
}

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.listeners.flags.protection;
import java.io.IOException;
import java.util.List;
import org.bukkit.Location;
@ -17,21 +18,38 @@ import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import com.google.common.base.Enums;
import com.google.common.base.Optional;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.util.Util;
/**
* Protects islands from visitors blowing things up
* @author tastybento
*/
public class TNTListener extends FlagListener {
/**
* Contains {@link EntityType}s that generates an explosion.
* @since 1.5.0
*/
private static final List<EntityType> TNT_TYPES = List.of(EntityType.PRIMED_TNT, EntityType.MINECART_TNT);
private static final List<EntityType> TNT_TYPES = List.of(
findFirstMatchingEnum(EntityType.class, "PRIMED_TNT", "TNT"),
findFirstMatchingEnum(EntityType.class, "MINECART_TNT", "TNT_MINECART"));
private static <T extends Enum<T>> T findFirstMatchingEnum(Class<T> enumClass, String... values) {
if (enumClass == null || values == null) {
return null;
}
for (String value : values) {
Optional<T> enumConstant = Enums.getIfPresent(enumClass, value.toUpperCase());
if (enumConstant.isPresent()) {
return enumConstant.get();
}
}
return null; // Return null if no match is found
}
/**
* Contains {@link Material}s that can be used to prime a TNT.
* @since 1.5.0

View File

@ -107,7 +107,7 @@ public class EnterExitListener extends FlagListener {
// Leave messages are always specific to this world
String islandMessage = user.getTranslation(island.getWorld(), ISLAND_MESSAGE, TextVariables.NAME, getPlugin().getPlayers().getName(island.getOwner()));
// Send specific message if the player is member of this island
if (island.getMemberSet().contains(user.getUniqueId())) {
if (island.inTeam(user.getUniqueId())) {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-leaving-your-island", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);
} else {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-leaving", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);
@ -135,7 +135,7 @@ public class EnterExitListener extends FlagListener {
// Enter messages are always specific to this world
String islandMessage = user.getTranslation(island.getWorld(), ISLAND_MESSAGE, TextVariables.NAME, getPlugin().getPlayers().getName(island.getOwner()));
// Send specific message if the player is member of this island
if (island.getMemberSet().contains(user.getUniqueId())) {
if (island.inTeam(user.getUniqueId())) {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-entering-your-island", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);
} else {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-entering", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);

View File

@ -17,6 +17,7 @@ import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.RayTraceResult;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.FlagListener;
@ -68,12 +69,20 @@ public class ObsidianScoopingListener extends FlagListener {
return lookForLava(e);
}
/**
* @param e PlayerInteractEvent
* @return false if obsidian not scooped, true if scooped
*/
private boolean lookForLava(PlayerInteractEvent e) {
Player player = e.getPlayer();
ItemStack bucket = e.getItem();
// Get block player is looking at
Block b = e.getPlayer().rayTraceBlocks(5, FluidCollisionMode.ALWAYS).getHitBlock();
RayTraceResult rtBlocks = e.getPlayer().rayTraceBlocks(5, FluidCollisionMode.ALWAYS);
if (rtBlocks == null) {
return false;
}
Block b = rtBlocks.getHitBlock();
if (!b.getType().equals(Material.OBSIDIAN)) {
// This should not be needed but might catch some attempts
return false;

View File

@ -31,7 +31,7 @@ public class PetTeleportListener extends FlagListener {
// Get where the pet is going
e.setCancelled(getIslands().getProtectedIslandAt(e.getTo())
// Not home island
.map(i -> !i.getMemberSet().contains(t.getOwner().getUniqueId()))
.map(i -> !i.inTeam(t.getOwner().getUniqueId()))
// Not any island
.orElse(true));
}

View File

@ -49,7 +49,7 @@ public class VisitorsStartingRaidListener extends FlagListener
Optional<Island> island = this.getIslands().getProtectedIslandAt(event.getPlayer().getLocation());
if (island.isPresent() && !island.get().getMemberSet().contains(event.getPlayer().getUniqueId()))
if (island.isPresent() && !island.get().inTeam(event.getPlayer().getUniqueId()))
{
event.setCancelled(true);
this.report(User.getInstance(event.getPlayer()),

View File

@ -17,6 +17,9 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
* Common Game Mode Placeholders
*/
public enum GameModePlaceholder {
/* World-related */

View File

@ -40,6 +40,7 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.metadata.MetaDataValue;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintPaster;
@ -484,6 +485,8 @@ public class BlueprintsManager {
b2 ->
pasteEnd(addon, bb, island).thenAccept(message -> sendMessage(island)).thenAccept(b3 -> Bukkit.getScheduler().runTask(plugin, task))));
}
// Set the bundle name
island.putMetaData("bundle", new MetaDataValue(name));
return true;
}

View File

@ -37,6 +37,10 @@ import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.github.puregero.multilib.MultiLib;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.papermc.lib.PaperLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
@ -44,9 +48,9 @@ import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.json.BentoboxTypeAdapterFactory;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.lists.Flags;
@ -65,29 +69,18 @@ public class IslandsManager {
private final BentoBox plugin;
/**
* One island can be spawn, this is the one - otherwise, this value is null
*/
@NonNull
private final Map<@NonNull World, @Nullable Island> spawn;
private Map<World, Island> spawns = new HashMap<>();
private Map<World, Location> last = new HashMap<>();
@NonNull
private Database<Island> handler;
/**
* The last locations where an island were put. This is not stored persistently
* and resets when the server starts
*/
private final Map<World, Location> last;
private static Database<Island> handler;
/**
* Island Cache
*/
@NonNull
private IslandCache islandCache;
// Quarantined islands
@NonNull
private final Map<UUID, List<Island>> quarantineCache;
// Deleted islands
@NonNull
private final List<String> deletedIslands;
@ -106,23 +99,56 @@ public class IslandsManager {
// Set up the database handler to store and retrieve Island classes
handler = new Database<>(plugin, Island.class);
islandCache = new IslandCache();
quarantineCache = new HashMap<>();
spawn = new HashMap<>();
last = new HashMap<>();
// This list should always be empty unless database deletion failed
// In that case a purge utility may be required in the future
deletedIslands = new ArrayList<>();
// Mid-teleport players going home
goingHome = new HashSet<>();
// Set handler in Island
// Listen for Island Updates
MultiLib.onString(plugin, "bentobox-updateIsland", id -> {
Island island = handler.loadObject(id);
if (island != null) {
islandCache.updateIsland(island);
}
});
// Delete island blocks
MultiLib.onString(plugin, "bentobox-deleteIsland", id -> {
IslandDeletion idd = getGson().fromJson(id, IslandDeletion.class);
plugin.getIslandDeletionManager().getIslandChunkDeletionManager().add(idd);
});
// List for new islands
MultiLib.onString(plugin, "bentobox-newIsland", id -> {
Island island = handler.loadObject(id);
if (island != null) {
islandCache.addIsland(island);
}
});
// Set or clear spawn
MultiLib.onString(plugin, "bentobox-setspawn", sp -> {
String[] split = sp.split(",");
if (split.length == 1) {
World world = Bukkit.getWorld(split[0]);
this.clearSpawn(world);
} else if (split.length == 2) {
World world = Bukkit.getWorld(split[0]);
if (world != null) {
getIslandById(split[1]).ifPresent(i -> this.setSpawn(i));
}
}
});
}
/**
* Used only for testing. Sets the database to a mock database.
*
* @param handler - handler
* @param h - handler
*/
public void setHandler(@NonNull Database<Island> handler) {
this.handler = handler;
public void setHandler(@NonNull Database<Island> h) {
handler = h;
}
/**
@ -227,14 +253,13 @@ public class IslandsManager {
.orElse("");
island.setGameMode(gmName);
island.setUniqueId(gmName + island.getUniqueId());
while (handler.objectExists(island.getUniqueId())) {
// This should never happen, so although this is a potential infinite loop I'm
// going to leave it here because
// it will be bad if this does occur and the server should crash.
plugin.logWarning("Duplicate island UUID occurred");
island.setUniqueId(gmName + UUID.randomUUID());
}
if (islandCache.addIsland(island)) {
// Save to database and notify other servers
handler.saveObjectAsync(island).thenAccept(b -> {
if (b.equals(Boolean.TRUE)) {
MultiLib.notify("bentobox-newIsland", island.getUniqueId());
}
});
return island;
}
return null;
@ -257,27 +282,40 @@ public class IslandsManager {
// Set the owner of the island to no one.
island.setOwner(null);
island.setFlag(Flags.LOCK, RanksManager.VISITOR_RANK);
island.setDeleted(true);
if (removeBlocks) {
// Remove island from the cache
islandCache.deleteIslandFromCache(island);
// Log the deletion (it shouldn't matter but may be useful)
island.log(new LogEntry.Builder("DELETED").build());
// Set the delete flag which will prevent it from being loaded even if database
// deletion fails
island.setDeleted(true);
// Save the island
handler.saveObjectAsync(island);
// Delete the island
handler.deleteObject(island);
// Remove players from island
removePlayersFromIsland(island);
if (!plugin.getSettings().isKeepPreviousIslandOnReset()) {
// Remove blocks from world
plugin.getIslandDeletionManager().getIslandChunkDeletionManager().add(new IslandDeletion(island));
IslandDeletion id = new IslandDeletion(island);
plugin.getIslandDeletionManager().getIslandChunkDeletionManager().add(id);
// Tell other servers
MultiLib.notify("bentobox-deleteIsland", getGson().toJson(id));
}
// Delete the island from the database
handler.deleteObject(island);
}
}
private Gson getGson() {
// Build the Gson
// excludeFieldsWithoutExposeAnnotation - this means that every field to be stored should use @Expose
// enableComplexMapKeySerialization - forces GSON to use TypeAdapters even for Map keys
GsonBuilder builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
.enableComplexMapKeySerialization().setPrettyPrinting();
// Register adapter factory
builder.registerTypeAdapterFactory(new BentoboxTypeAdapterFactory(plugin));
// Allow characters like < or > without escaping them
builder.disableHtmlEscaping();
return builder.create();
}
/**
* Get the number of islands made on this server. Used by stats.
*
@ -321,7 +359,7 @@ public class IslandsManager {
* @return List of islands or empty list if none found for user
*/
@NonNull
public Set<Island> getIslands(@NonNull World world, @NonNull User user) {
public List<Island> getIslands(@NonNull World world, @NonNull User user) {
return getIslands(world, user.getUniqueId());
}
@ -333,7 +371,7 @@ public class IslandsManager {
* @return List of islands or empty list if none found for user
*/
@NonNull
public Set<Island> getIslands(@NonNull World world, UUID uniqueId) {
public List<Island> getIslands(@NonNull World world, UUID uniqueId) {
return islandCache.getIslands(world, uniqueId);
}
@ -462,7 +500,7 @@ public class IslandsManager {
* Get the last location where an island was created
*
* @param world - world
* @return location
* @return location or null if none found
*/
public Location getLast(@NonNull World world) {
return last.get(world);
@ -486,7 +524,7 @@ public class IslandsManager {
if (island.getOwner() == null) {
// No owner, no rank settings
island.setMaxMembers(null);
this.save(island);
updateIsland(island);
return 0;
}
// Island max is either the world default or specified amount for this island
@ -507,8 +545,11 @@ public class IslandsManager {
islandMax = owner.getPermissionValue(plugin.getIWM().getPermissionPrefix(island.getWorld()) + perm,
islandMax);
}
island.setMaxMembers(rank, islandMax == worldDefault ? null : islandMax);
this.save(island);
Integer change = islandMax == worldDefault ? null : islandMax;
if (island.getMaxMembers().get(rank) != change) {
island.setMaxMembers(rank, change);
updateIsland(island);
}
return islandMax;
}
@ -546,13 +587,16 @@ public class IslandsManager {
}
// If the island maxHomes is just the same as the world default, then set to
// null
island.setMaxHomes(islandMax == plugin.getIWM().getMaxHomes(island.getWorld()) ? null : islandMax);
this.save(island);
Integer change = islandMax == plugin.getIWM().getMaxHomes(island.getWorld()) ? null : islandMax;
if (island.getMaxHomes() != change) {
island.setMaxHomes(change);
updateIsland(island);
}
return islandMax;
}
/**
* Set the maximum numbber of homes allowed on this island
* Set the maximum number of homes allowed on this island
*
* @param island - island
* @param maxHomes - max number of homes allowed, or null if the world default
@ -735,9 +779,9 @@ public class IslandsManager {
* @since 1.16.0
*/
public boolean setHomeLocation(@Nullable Island island, Location location, String name) {
if (island != null) {
if (island != null && (island.getHome(name) == null || !island.getHome(name).equals(location))) {
island.addHome(name, location);
this.save(island);
updateIsland(island);
return true;
}
return false;
@ -890,7 +934,7 @@ public class IslandsManager {
*/
@NonNull
public Optional<Island> getSpawn(@NonNull World world) {
return Optional.ofNullable(spawn.get(world));
return Optional.ofNullable(spawns.get(world));
}
/**
@ -901,7 +945,7 @@ public class IslandsManager {
*/
@Nullable
public Location getSpawnPoint(@NonNull World world) {
return spawn.containsKey(world) ? spawn.get(world).getSpawnPoint(world.getEnvironment()) : null;
return getSpawn(world).map(i -> i.getSpawnPoint(world.getEnvironment())).orElse(null);
}
/**
@ -1132,7 +1176,7 @@ public class IslandsManager {
* @return true if they are, false if they are not, or spawn does not exist
*/
public boolean isAtSpawn(Location playerLoc) {
return spawn.containsKey(playerLoc.getWorld()) && spawn.get(playerLoc.getWorld()).onIsland(playerLoc);
return getSpawn(playerLoc.getWorld()).map(i -> i.onIsland(playerLoc)).orElse(false);
}
/**
@ -1144,19 +1188,14 @@ public class IslandsManager {
* @param spawn the Island to set as spawn. Must not be null.
*/
public void setSpawn(@NonNull Island spawn) {
// Checking if there is already a spawn set for this world
if (this.spawn.containsKey(spawn.getWorld()) && this.spawn.get(spawn.getWorld()) != null) {
Island oldSpawn = this.spawn.get(spawn.getWorld());
if (oldSpawn.equals(spawn)) {
return; // The spawn is already the current spawn - no need to update anything.
} else {
oldSpawn.setSpawn(false);
}
if (spawn.getWorld() != null) {
spawns.put(Util.getWorld(spawn.getWorld()), spawn);
// Tell other servers
MultiLib.notify("bentobox-setspawn", spawn.getWorld().getUID().toString() + "," + spawn.getUniqueId());
}
this.spawn.put(spawn.getWorld(), spawn);
spawn.setSpawn(true);
}
/**
* Clears the spawn island for this world
*
@ -1164,11 +1203,9 @@ public class IslandsManager {
* @since 1.8.0
*/
public void clearSpawn(World world) {
Island spawnIsland = spawn.get(Util.getWorld(world));
if (spawnIsland != null) {
spawnIsland.setSpawn(false);
}
this.spawn.remove(world);
spawns.remove(world);
// Tell other servers
MultiLib.notify("bentobox-setspawn", world.getUID().toString());
}
/**
@ -1192,7 +1229,6 @@ public class IslandsManager {
*/
public void load() throws IOException {
islandCache.clear();
quarantineCache.clear();
List<Island> toQuarantine = new ArrayList<>();
int owned = 0;
int unowned = 0;
@ -1206,9 +1242,6 @@ public class IslandsManager {
if (island.isDeleted()) {
// These will be deleted later
deletedIslands.add(island.getUniqueId());
} else if (island.isDoNotLoad() && island.getWorld() != null && island.getCenter() != null) {
// Add to quarantine cache
quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island);
} // Check island distance and if incorrect stop BentoBox
else if (island.getWorld() != null && plugin.getIWM().inWorld(island.getWorld())
&& island.getRange() != plugin.getIWM().getIslandDistance(island.getWorld())) {
@ -1219,18 +1252,9 @@ public class IslandsManager {
} else {
// Fix island center if it is off
fixIslandCenter(island);
if (!islandCache.addIsland(island)) {
// Quarantine the offending island
toQuarantine.add(island);
// Add to quarantine cache
island.setDoNotLoad(true);
quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island);
if (island.isUnowned()) {
unowned++;
} else {
owned++;
}
} else if (island.isSpawn()) {
islandCache.addIsland(island);
if (island.isSpawn()) {
// Success, set spawn if this is the spawn island.
this.setSpawn(island);
} else {
@ -1319,7 +1343,7 @@ public class IslandsManager {
return false;
}
// Get the player's island
return getIslandAt(loc).filter(i -> i.onIsland(loc)).map(i -> i.getMemberSet().contains(player.getUniqueId()))
return getIslandAt(loc).filter(i -> i.onIsland(loc)).map(i -> i.inTeam(player.getUniqueId()))
.orElse(false);
}
@ -1389,20 +1413,15 @@ public class IslandsManager {
.filter(p -> p.getGameMode().equals(plugin.getIWM().getDefaultGameMode(island.getWorld())))
.filter(p -> island.onIsland(p.getLocation())).forEach(p -> {
// Teleport island players to their island home
if (!island.getMemberSet().contains(p.getUniqueId())
if (!island.inTeam(p.getUniqueId())
&& (hasIsland(w, p.getUniqueId()) || inTeam(w, p.getUniqueId()))) {
homeTeleportAsync(w, p);
} else {
// Move player to spawn
if (spawn.containsKey(w)) {
// go to island spawn
Location sp = spawn.get(w).getSpawnPoint(w.getEnvironment());
if (sp != null) {
PaperLib.teleportAsync(p, sp);
} else {
plugin.logWarning("Spawn exists but its location is null!");
}
}
getSpawn(w).map(i -> i.getSpawnPoint(w.getEnvironment())).filter(Objects::nonNull)
.ifPresentOrElse(sp -> PaperLib.teleportAsync(p, sp),
() -> plugin.logWarning("Spawn exists but its location is null!"));
}
});
}
@ -1473,9 +1492,13 @@ public class IslandsManager {
teamIsland.addMember(playerUUID);
islandCache.addPlayer(playerUUID, teamIsland);
// Save the island
handler.saveObjectAsync(teamIsland);
updateIsland(teamIsland);
}
/**
* Set the last island location
* @param last location
*/
public void setLast(Location last) {
this.last.put(last.getWorld(), last);
}
@ -1493,8 +1516,9 @@ public class IslandsManager {
}
/**
* Checks if a player is in a team in this world. Note that the player may have
* Checks if a player is in any team in this world. Note that the player may have
* multiple islands in the world, any one of which may have a team.
* Consider checking the island itself {@link Island#inTeam(UUID)}
*
* @param world - world
* @param playerUUID - player's UUID
@ -1502,7 +1526,7 @@ public class IslandsManager {
*/
public boolean inTeam(World world, @NonNull UUID playerUUID) {
return this.islandCache.getIslands(world, playerUUID).stream()
.anyMatch(island -> island.getMemberSet().size() > 1 && island.getMemberSet().contains(playerUUID));
.anyMatch(island -> island.getMemberSet().size() > 1 && island.inTeam(playerUUID));
}
/**
@ -1607,12 +1631,16 @@ public class IslandsManager {
}
/**
* Save the island to the database
* Update island data in database
*
* @param island - island
*/
public void save(Island island) {
handler.saveObjectAsync(island);
public static void updateIsland(Island island) {
if (handler.objectExists(island.getUniqueId())) {
island.clearChanged();
handler.saveObjectAsync(island)
.thenAccept(b -> MultiLib.notify("bentobox-updateIsland", island.getUniqueId()));
}
}
/**
@ -1627,108 +1655,6 @@ public class IslandsManager {
return Optional.ofNullable(islandCache.getIslandById(uniqueId));
}
/**
* Try to get an unmodifiable list of quarantined islands owned by uuid in this
* world
*
* @param world - world
* @param uuid - target player's UUID, or <tt>null</tt> = unowned islands
* @return list of islands; may be empty
* @since 1.3.0
*/
@NonNull
public List<Island> getQuarantinedIslandByUser(@NonNull World world, @Nullable UUID uuid) {
return quarantineCache.getOrDefault(uuid, Collections.emptyList()).stream()
.filter(i -> i.getWorld().equals(world)).toList();
}
/**
* Delete quarantined islands owned by uuid in this world
*
* @param world - world
* @param uuid - target player's UUID, or <tt>null</tt> = unowned islands
* @since 1.3.0
*/
public void deleteQuarantinedIslandByUser(World world, @Nullable UUID uuid) {
if (quarantineCache.containsKey(uuid)) {
quarantineCache.get(uuid).stream().filter(i -> i.getWorld().equals(world))
.forEach(i -> handler.deleteObject(i));
quarantineCache.get(uuid).removeIf(i -> i.getWorld().equals(world));
}
}
/**
* @return the quarantineCache
* @since 1.3.0
*/
@NonNull
public Map<UUID, List<Island>> getQuarantineCache() {
return quarantineCache;
}
/**
* Remove a quarantined island and delete it from the database completely. This
* is NOT recoverable unless you have database backups.
*
* @param island island
* @return {@code true} if island is quarantined and removed
* @since 1.3.0
*/
public boolean purgeQuarantinedIsland(Island island) {
if (quarantineCache.containsKey(island.getOwner()) && quarantineCache.get(island.getOwner()).remove(island)) {
handler.deleteObject(island);
return true;
}
return false;
}
/**
* Switches active island and island in trash
*
* @param world - game world
* @param target - target player's UUID
* @param island - island in trash
* @return <tt>true</tt> if successful, otherwise <tt>false</tt>
* @since 1.3.0
*/
public boolean switchIsland(World world, UUID target, Island island) {
// Remove trashed island from trash
if (!quarantineCache.containsKey(island.getOwner()) || !quarantineCache.get(island.getOwner()).remove(island)) {
plugin.logError("Could not remove island from trash");
return false;
}
// Remove old island from cache if it exists
if (this.hasIsland(world, target)) {
Island oldIsland = islandCache.get(world, target);
islandCache.removeIsland(oldIsland);
// Set old island to trash
oldIsland.setDoNotLoad(true);
// Put old island into trash
quarantineCache.computeIfAbsent(target, k -> new ArrayList<>()).add(oldIsland);
// Save old island
handler.saveObjectAsync(oldIsland).thenAccept(result -> {
if (Boolean.FALSE.equals(result))
plugin.logError("Could not save trashed island in database");
});
}
// Restore island from trash
island.setDoNotLoad(false);
// Add new island to cache
if (!islandCache.addIsland(island)) {
plugin.logError("Could not add recovered island to cache");
return false;
}
// Save new island
handler.saveObjectAsync(island).thenAccept(result -> {
if (Boolean.FALSE.equals(result)) {
plugin.logError("Could not save recovered island to database");
}
});
return true;
}
/**
* Resets all flags to gamemode config.yml default
*

View File

@ -3,6 +3,9 @@ package world.bentobox.bentobox.managers;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -13,6 +16,7 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.placeholders.PlaceholderReplacer;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
@ -72,6 +76,50 @@ public class PlaceholdersManager {
.forEach(placeholder -> registerPlaceholder(addon, placeholder.getPlaceholder(), new DefaultPlaceholder(addon, placeholder)));
// Register team member placeholders
registerTeamMemberPlaceholders(addon);
// Register potential island names and member info
registerOwnedIslandPlaceholders(addon);
}
private void registerOwnedIslandPlaceholders(@NonNull GameModeAddon addon) {
int maxIslands = plugin.getIWM().getWorldSettings(addon.getOverWorld()).getConcurrentIslands();
IntStream.range(0, maxIslands).forEach(i -> registerPlaceholder(addon, "island_name_" + (i + 1), user -> {
if (user == null)
return "";
AtomicInteger generatedCount = new AtomicInteger(1); // To increment within lambda
return plugin.getIslands().getIslands(addon.getOverWorld(), user).stream().map(island -> {
IslandName islandName = getIslandName(island, user, generatedCount.get());
if (islandName.generatated()) {
generatedCount.getAndIncrement(); // Increment if the name was generated
}
return islandName.name;
}).skip(i) // Skip to the island at index 'i'
.findFirst() // Take the first island after skipping, effectively the (i+1)th
.orElse(""); // Default to empty string if no island is found
}));
// Island_memberlist
IntStream.range(0, maxIslands)
.forEach(i -> registerPlaceholder(addon, "island_memberlist_" + (i + 1), user -> user == null ? ""
: plugin.getIslands().getIslands(addon.getOverWorld(), user).stream().skip(i).findFirst()
.map(island -> island.getMemberSet().stream()
.map(addon.getPlayers()::getName).collect(Collectors.joining(",")))
.orElse("")));
}
private record IslandName(String name, boolean generatated) {
}
private IslandName getIslandName(Island island, User user, int index) {
if (island.getName() != null && !island.getName().isBlank()) {
// Name has been set
return new IslandName(island.getName(), false);
} else {
// Name has not been set
return new IslandName(user.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME,
user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName()) + " " + index, true);
}
}
private void registerTeamMemberPlaceholders(@NonNull GameModeAddon addon) {

View File

@ -4,21 +4,18 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.scheduler.BukkitRunnable;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
@ -31,11 +28,9 @@ public class PlayersManager {
private final BentoBox plugin;
private Database<Players> handler;
private final Database<Names> names;
private final Map<UUID, Players> playerCache = new HashMap<>();
private final Map<UUID, Players> playerCache;
private final Set<UUID> inTeleport;
private boolean isSaveTaskRunning;
private final Set<UUID> inTeleport; // this needs databasing
/**
* Provides a memory cache of online player information
@ -50,7 +45,6 @@ public class PlayersManager {
handler = new Database<>(plugin, Players.class);
// Set up the names database
names = new Database<>(plugin, Names.class);
playerCache = new HashMap<>();
inTeleport = new HashSet<>();
}
@ -62,67 +56,7 @@ public class PlayersManager {
this.handler = handler;
}
/**
* Load all players - not normally used as to load all players into memory will be wasteful
*/
public void load(){
playerCache.clear();
inTeleport.clear();
handler.loadObjects().forEach(p -> playerCache.put(p.getPlayerUUID(), p));
}
public boolean isSaveTaskRunning() {
return isSaveTaskRunning;
}
/**
* Save all players
*/
public void saveAll() {
saveAll(false);
}
/**
* Save all players
* @param schedule true if we should let the task run over multiple ticks to reduce lag spikes
*/
public void saveAll(boolean schedule){
if (!schedule) {
for (Players player : playerCache.values()) {
try {
handler.saveObjectAsync(player);
} catch (Exception e) {
plugin.logError("Could not save player to database when running sync! " + e.getMessage());
}
}
return;
}
isSaveTaskRunning = true;
Queue<Players> queue = new LinkedList<>(playerCache.values());
new BukkitRunnable() {
@Override
public void run() {
for (int i = 0; i < plugin.getSettings().getMaxSavedPlayersPerTick(); i++) {
Players player = queue.poll();
if (player == null) {
isSaveTaskRunning = false;
cancel();
return;
}
try {
handler.saveObjectAsync(player);
} catch (Exception e) {
plugin.logError("Could not save player to database when running sync! " + e.getMessage());
}
}
}
}.runTaskTimer(plugin, 0, 1);
}
public void shutdown(){
saveAll();
playerCache.clear();
handler.close();
}
@ -134,11 +68,29 @@ public class PlayersManager {
@Nullable
public Players getPlayer(UUID uuid){
if (!playerCache.containsKey(uuid)) {
addPlayer(uuid);
playerCache.put(uuid, addPlayer(uuid));
}
return playerCache.get(uuid);
}
/**
* Adds a player to the database. If the UUID does not exist, a new player is made
* @param playerUUID - the player's UUID
*/
private Players addPlayer(@NonNull UUID playerUUID) {
Objects.requireNonNull(playerUUID);
// If the player is in the database, load it, otherwise create a new player
if (handler.objectExists(playerUUID.toString())) {
Players player = handler.loadObject(playerUUID.toString());
if (player != null) {
return player;
}
}
Players player = new Players(plugin, playerUUID);
handler.saveObject(player);
return player;
}
/**
* Returns an <strong>unmodifiable collection</strong> of all the players that are <strong>currently in the cache</strong>.
* @return unmodifiable collection containing every player in the cache.
@ -146,37 +98,7 @@ public class PlayersManager {
*/
@NonNull
public Collection<Players> getPlayers() {
return Collections.unmodifiableCollection(playerCache.values());
}
/*
* Cache control methods
*/
/**
* Adds a player to the cache. If the UUID does not exist, a new player is made
* @param playerUUID - the player's UUID
*/
public void addPlayer(UUID playerUUID) {
if (playerUUID == null) {
return;
}
if (!playerCache.containsKey(playerUUID)) {
Players player;
// If the player is in the database, load it, otherwise create a new player
if (handler.objectExists(playerUUID.toString())) {
player = handler.loadObject(playerUUID.toString());
if (player == null) {
player = new Players(plugin, playerUUID);
// Corrupted database entry
plugin.logError("Corrupted player database entry for " + playerUUID + " - unrecoverable. Recreated.");
player.setUniqueId(playerUUID.toString());
}
} else {
player = new Players(plugin, playerUUID);
}
playerCache.put(playerUUID, player);
}
return Collections.unmodifiableCollection(handler.loadObjects());
}
/**
@ -187,7 +109,7 @@ public class PlayersManager {
* @return true if player is known, otherwise false
*/
public boolean isKnown(UUID uniqueID) {
return uniqueID != null && (playerCache.containsKey(uniqueID) || handler.objectExists(uniqueID.toString()));
return uniqueID == null ? false : handler.objectExists(uniqueID.toString());
}
/**
@ -206,11 +128,8 @@ public class PlayersManager {
// Not used
}
}
// Look in the name cache, then the data base and then give up
return playerCache.values().stream()
.filter(p -> p.getPlayerName().equalsIgnoreCase(name)).findFirst()
.map(p -> UUID.fromString(p.getUniqueId()))
.orElseGet(() -> names.objectExists(name) ? names.loadObject(name).getUuid() : null);
return names.loadObjects().stream().filter(n -> n.getUniqueId().equalsIgnoreCase(name)).findFirst()
.map(Names::getUuid).orElse(null);
}
/**
@ -218,8 +137,9 @@ public class PlayersManager {
* @param user - the User
*/
public void setPlayerName(@NonNull User user) {
addPlayer(user.getUniqueId());
playerCache.get(user.getUniqueId()).setPlayerName(user.getName());
Players player = getPlayer(user.getUniqueId());
player.setPlayerName(user.getName());
handler.saveObject(player);
Names newName = new Names(user.getName(), user.getUniqueId());
// Add to names database
names.saveObjectAsync(newName);
@ -237,8 +157,8 @@ public class PlayersManager {
if (playerUUID == null) {
return "";
}
addPlayer(playerUUID);
return playerCache.get(playerUUID).getPlayerName();
return names.loadObjects().stream().filter(n -> n.getUuid().equals(playerUUID)).findFirst()
.map(Names::getUniqueId).orElse(null);
}
/**
@ -248,8 +168,7 @@ public class PlayersManager {
* @return number of resets
*/
public int getResets(World world, UUID playerUUID) {
addPlayer(playerUUID);
return playerCache.get(playerUUID).getResets(world);
return getPlayer(playerUUID).getResets(world);
}
/**
@ -261,7 +180,7 @@ public class PlayersManager {
* @see #getResets(World, UUID)
*/
public int getResetsLeft(World world, UUID playerUUID) {
addPlayer(playerUUID);
getPlayer(playerUUID);
if (plugin.getIWM().getResetLimit(world) == -1) {
return -1;
} else {
@ -277,8 +196,9 @@ public class PlayersManager {
* @param resets number of resets to set
*/
public void setResets(World world, UUID playerUUID, int resets) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setResets(world, resets);
Players p = getPlayer(playerUUID);
p.setResets(world, resets);
handler.saveObject(p);
}
/**
@ -287,11 +207,7 @@ public class PlayersManager {
* @return name of the locale this player uses
*/
public String getLocale(UUID playerUUID) {
addPlayer(playerUUID);
if (playerUUID == null) {
return "";
}
return playerCache.get(playerUUID).getLocale();
return getPlayer(playerUUID).getLocale();
}
/**
@ -300,8 +216,9 @@ public class PlayersManager {
* @param localeName - locale name, e.g., en-US
*/
public void setLocale(UUID playerUUID, String localeName) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setLocale(localeName);
Players p = getPlayer(playerUUID);
p.setLocale(localeName);
handler.saveObject(p);
}
/**
@ -310,8 +227,9 @@ public class PlayersManager {
* @param playerUUID - the player's UUID
*/
public void addDeath(World world, UUID playerUUID) {
addPlayer(playerUUID);
playerCache.get(playerUUID).addDeath(world);
Players p = getPlayer(playerUUID);
p.addDeath(world);
handler.saveObject(p);
}
/**
@ -321,8 +239,9 @@ public class PlayersManager {
* @param deaths - number of deaths
*/
public void setDeaths(World world, UUID playerUUID, int deaths) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setDeaths(world, deaths);
Players p = getPlayer(playerUUID);
p.setDeaths(world, deaths);
handler.saveObject(p);
}
/**
@ -332,8 +251,7 @@ public class PlayersManager {
* @return number of deaths
*/
public int getDeaths(World world, UUID playerUUID) {
addPlayer(playerUUID);
return playerCache.get(playerUUID) == null ? 0 : playerCache.get(playerUUID).getDeaths(world);
return getPlayer(playerUUID).getDeaths(world);
}
/**
@ -360,16 +278,6 @@ public class PlayersManager {
return inTeleport.contains(uniqueId);
}
/**
* Saves the player to the database
* @param playerUUID - the player's UUID
*/
public void save(UUID playerUUID) {
if (playerCache.containsKey(playerUUID)) {
handler.saveObjectAsync(playerCache.get(playerUUID));
}
}
/**
* Tries to get the user from his name
* @param name - name
@ -395,41 +303,17 @@ public class PlayersManager {
* @param playerUUID player's UUID
*/
public void addReset(World world, UUID playerUUID) {
addPlayer(playerUUID);
playerCache.get(playerUUID).addReset(world);
Players p = getPlayer(playerUUID);
p.addReset(world);
handler.saveObject(p);
}
/**
* Sets the Flags display mode for the Settings Panel for this player.
* @param playerUUID player's UUID
* @param displayMode the {@link Flag.Mode} to set
* @since 1.6.0
*/
public void setFlagsDisplayMode(UUID playerUUID, Flag.Mode displayMode) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setFlagsDisplayMode(displayMode);
}
/**
* Returns the Flags display mode for the Settings Panel for this player.
* @param playerUUID player's UUID
* @return the {@link Flag.Mode display mode} for the Flags in the Settings Panel.
* @since 1.6.0
*/
public Flag.Mode getFlagsDisplayMode(UUID playerUUID) {
addPlayer(playerUUID);
return playerCache.get(playerUUID).getFlagsDisplayMode();
}
/**
* Remove player from cache. Clears players with the same name or UUID
* Remove player from database
* @param player player to remove
*/
public void removePlayer(Player player) {
// Clear any players with the same name
playerCache.values().removeIf(p -> player.getName().equalsIgnoreCase(p.getPlayerName()));
// Remove if the player's UUID is the same
playerCache.values().removeIf(p -> player.getUniqueId().toString().equals(p.getUniqueId()));
handler.deleteID(player.getUniqueId().toString());
}
/**
@ -495,8 +379,6 @@ public class PlayersManager {
// Player total XP (not displayed)
target.getPlayer().setTotalExperience(0);
}
// Save player
save(target.getUniqueId());
}
}

View File

@ -1,10 +1,13 @@
package world.bentobox.bentobox.managers.island;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -27,8 +30,6 @@ import world.bentobox.bentobox.util.Util;
* @author tastybento
*/
public class IslandCache {
@NonNull
private final Map<@NonNull Location, @NonNull Island> islandsByLocation;
/**
* Map of all islands with island uniqueId as key
*/
@ -45,12 +46,47 @@ public class IslandCache {
private final Map<@NonNull World, @NonNull IslandGrid> grids;
public IslandCache() {
islandsByLocation = new HashMap<>();
islandsById = new HashMap<>();
islandsByUUID = new HashMap<>();
grids = new HashMap<>();
}
/**
* Replace the island we have with this one
* @param newIsland island
*/
public void updateIsland(@NonNull Island newIsland) {
if (newIsland.isDeleted()) {
this.deleteIslandFromCache(newIsland);
return;
}
// Get the old island
Island oldIsland = islandsById.get(newIsland.getUniqueId());
Set<UUID> newMembers = newIsland.getMembers().keySet();
if (oldIsland != null) {
Set<UUID> oldMembers = oldIsland.getMembers().keySet();
// Remove any members who are not in the new island
for (UUID oldMember : oldMembers) {
if (!newMembers.contains(oldMember)) {
// Member has been removed - remove island
islandsByUUID.computeIfAbsent(oldMember, k -> new HashSet<>()).remove(oldIsland);
}
}
}
// Update the members with the new island object
for (UUID newMember : newMembers) {
Set<Island> set = islandsByUUID.computeIfAbsent(newMember, k -> new HashSet<>());
set.remove(oldIsland);
set.add(newIsland);
islandsByUUID.put(newMember, set);
}
if (islandsById.put(newIsland.getUniqueId(), newIsland) == null) {
BentoBox.getInstance().logError("islandsById failed to update");
}
}
/**
* Adds an island to the grid
*
@ -59,15 +95,9 @@ public class IslandCache {
*/
public boolean addIsland(@NonNull Island island) {
if (island.getCenter() == null || island.getWorld() == null) {
/*
* Special handling - return true. The island will not be quarantined, but just
* not loaded This can occur when a gamemode is removed temporarily from the
* server TODO: have an option to remove these when the purge command is added
*/
return true;
return false;
}
if (addToGrid(island)) {
islandsByLocation.put(island.getCenter(), island);
islandsById.put(island.getUniqueId(), island);
// Only add islands to this map if they are owned
if (island.isOwned()) {
@ -97,11 +127,10 @@ public class IslandCache {
* @return true if successfully added, false if not
*/
private boolean addToGrid(@NonNull Island newIsland) {
return grids.computeIfAbsent(newIsland.getWorld(), k -> new IslandGrid()).addToGrid(newIsland);
return grids.computeIfAbsent(newIsland.getWorld(), k -> new IslandGrid(this)).addToGrid(newIsland);
}
public void clear() {
islandsByLocation.clear();
islandsById.clear();
islandsByUUID.clear();
}
@ -110,29 +139,19 @@ public class IslandCache {
* Deletes an island from the cache. Does not remove blocks.
*
* @param island island to delete
* @return true if successful, false if not
*/
public boolean deleteIslandFromCache(@NonNull Island island) {
if (!islandsByLocation.remove(island.getCenter(), island)) {
return false;
}
islandsById.remove(island.getUniqueId());
public void deleteIslandFromCache(@NonNull Island island) {
islandsById.remove(island.getUniqueId(), island);
removeFromIslandsByUUID(island);
// Remove from grid
grids.putIfAbsent(island.getWorld(), new IslandGrid());
return grids.get(island.getWorld()).removeFromGrid(island);
if (grids.containsKey(island.getWorld())) {
grids.get(island.getWorld()).removeFromGrid(island);
}
}
private void removeFromIslandsByUUID(Island island) {
for (Set<Island> set : islandsByUUID.values()) {
Iterator<Island> is = set.iterator();
while (is.hasNext()) {
Island i = is.next();
if (i.equals(island)) {
is.remove();
}
}
// set.removeIf(island::equals);
set.removeIf(island::equals);
}
}
@ -142,24 +161,11 @@ public class IslandCache {
* @param uniqueId - island unique ID
*/
public void deleteIslandFromCache(@NonNull String uniqueId) {
islandsById.remove(uniqueId);
islandsByLocation.values().removeIf(i -> i.getUniqueId().equals(uniqueId));
for (Set<Island> set : islandsByUUID.values()) {
set.removeIf(i -> i.getUniqueId().equals(uniqueId));
if (islandsById.containsKey(uniqueId)) {
deleteIslandFromCache(islandsById.get(uniqueId));
}
}
/**
* Get island based on the exact center location of the island
*
* @param location location to search for
* @return island or null if it does not exist
*/
@Nullable
public Island get(@NonNull Location location) {
return islandsByLocation.get(location);
}
/**
* Returns island referenced by player's UUID. Returns the island the player is
* on now, or their last known island
@ -170,18 +176,18 @@ public class IslandCache {
*/
@Nullable
public Island get(@NonNull World world, @NonNull UUID uuid) {
Set<Island> islands = getIslands(world, uuid);
List<Island> islands = getIslands(world, uuid);
if (islands.isEmpty()) {
return null;
}
for (Island island : islands) {
if (island.isPrimary()) {
if (island.isPrimary(uuid)) {
return island;
}
}
// If there is no primary set, then set one - it doesn't matter which.
Island result = islands.iterator().next();
result.setPrimary(true);
result.setPrimary(uuid);
return result;
}
@ -190,15 +196,16 @@ public class IslandCache {
*
* @param world world to check. Includes nether and end worlds.
* @param uuid player's UUID
* @return list of island or empty list if none
* @return list of island or empty list if none sorted from oldest to youngest
*/
public Set<Island> getIslands(@NonNull World world, @NonNull UUID uuid) {
public List<Island> getIslands(@NonNull World world, @NonNull UUID uuid) {
World w = Util.getWorld(world);
if (w == null) {
return new HashSet<>();
return new ArrayList<>();
}
return islandsByUUID.computeIfAbsent(uuid, k -> new HashSet<>()).stream().filter(i -> w.equals(i.getWorld()))
.collect(Collectors.toSet());
return islandsByUUID.computeIfAbsent(uuid, k -> new HashSet<>()).stream().filter(island -> w.equals(island.getWorld()))
.sorted(Comparator.comparingLong(Island::getCreatedDate))
.collect(Collectors.toList());
}
/**
@ -208,8 +215,16 @@ public class IslandCache {
* @param island island to make primary
*/
public void setPrimaryIsland(@NonNull UUID uuid, @NonNull Island island) {
if (island.getPrimaries().contains(uuid)) {
return;
}
for (Island is : getIslands(island.getWorld(), uuid)) {
is.setPrimary(island.equals(is));
if (is.getPrimaries().contains(uuid)) {
is.removePrimary(uuid);
}
if (is.equals(island)) {
is.setPrimary(uuid);
}
}
}
@ -237,7 +252,7 @@ public class IslandCache {
*/
@NonNull
public Collection<Island> getIslands() {
return Collections.unmodifiableCollection(islandsByLocation.values());
return Collections.unmodifiableCollection(islandsById.values());
}
/**
@ -255,8 +270,8 @@ public class IslandCache {
if (overworld == null) {
return Collections.emptyList();
}
return islandsByLocation.entrySet().stream()
.filter(entry -> overworld.equals(Util.getWorld(entry.getKey().getWorld()))) // shouldn't make NPEs
return islandsById.entrySet().stream()
.filter(entry -> overworld.equals(Util.getWorld(entry.getValue().getWorld()))) // shouldn't make NPEs
.map(Map.Entry::getValue).toList();
}
@ -322,6 +337,7 @@ public class IslandCache {
islandSet.remove(island);
}
island.removeMember(uuid);
island.removePrimary(uuid);
}
/**
@ -330,7 +346,7 @@ public class IslandCache {
* @return the number of islands
*/
public int size() {
return islandsByLocation.size();
return islandsById.size();
}
/**
@ -340,7 +356,7 @@ public class IslandCache {
* @return the number of islands
*/
public long size(World world) {
return this.islandsByLocation.keySet().stream().map(Location::getWorld).filter(world::equals).count();
return this.islandsById.values().stream().map(Island::getWorld).filter(world::equals).count();
}
/**
@ -355,7 +371,6 @@ public class IslandCache {
islandsByUUID.computeIfAbsent(newOwnerUUID, k -> new HashSet<>()).add(island);
}
island.setRank(newOwnerUUID, RanksManager.OWNER_RANK);
islandsByLocation.put(island.getCenter(), island);
islandsById.put(island.getUniqueId(), island);
}
@ -371,27 +386,6 @@ public class IslandCache {
return islandsById.get(uniqueId);
}
/**
* Removes an island from the cache completely without altering the island
* object
*
* @param island - island to remove
* @since 1.3.0
*/
public void removeIsland(@NonNull Island island) {
islandsByLocation.values().removeIf(island::equals);
islandsById.values().removeIf(island::equals);
islandsByUUID.values().removeIf(island::equals);
World w = Util.getWorld(island.getWorld());
if (w == null) {
return;
}
if (grids.containsKey(w)) {
grids.get(w).removeFromGrid(island);
}
}
/**
* Resets all islands in this game mode to default flag settings
*

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.managers.island;
import java.util.Map.Entry;
import java.util.TreeMap;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island;
/**
@ -12,8 +11,16 @@ import world.bentobox.bentobox.database.objects.Island;
*
*/
class IslandGrid {
private final TreeMap<Integer, TreeMap<Integer, Island>> grid = new TreeMap<>();
private final BentoBox plugin = BentoBox.getInstance();
private final TreeMap<Integer, TreeMap<Integer, String>> grid = new TreeMap<>();
private final IslandCache im;
/**
* @param im IslandsManager
*/
public IslandGrid(IslandCache im) {
super();
this.im = im;
}
/**
* Adds island to grid
@ -21,50 +28,23 @@ class IslandGrid {
* @return true if successfully added, false if island already exists, or there is an overlap
*/
public boolean addToGrid(Island island) {
// Check if we know about this island already
if (grid.containsKey(island.getMinX())) {
TreeMap<Integer, Island> zEntry = grid.get(island.getMinX());
TreeMap<Integer, String> zEntry = grid.get(island.getMinX());
if (zEntry.containsKey(island.getMinZ())) {
// There is an overlap or duplicate
plugin.logError("Cannot load island. Overlapping: " + island.getUniqueId());
plugin.logError("Location: " + island.getCenter());
// Get the previously loaded island
Island firstLoaded = zEntry.get(island.getMinZ());
if (firstLoaded.getOwner() == null && island.getOwner() != null) {
// This looks fishy. We prefer to load islands that have an owner. Swap the two
plugin.logError("Duplicate island has an owner, so using that one. " + island.getOwner());
firstLoaded = new Island(island);
zEntry.put(island.getMinZ(), firstLoaded);
} else if (firstLoaded.getOwner() != null && island.getOwner() != null) {
// Check if the owners are the same - this is a true duplicate
if (firstLoaded.getOwner().equals(island.getOwner())) {
// Find out which one is the original
if (firstLoaded.getCreatedDate() > island.getCreatedDate()) {
plugin.logError("Same owner duplicate. Swapping based on creation date.");
// FirstLoaded is the newer
firstLoaded = new Island(island);
zEntry.put(island.getMinZ(), firstLoaded);
} else {
plugin.logError("Same owner duplicate.");
}
} else {
plugin.logError("Duplicate but different owner. Keeping first loaded.");
plugin.logError("This is serious!");
plugin.logError("1st loaded ID: " + firstLoaded.getUniqueId());
plugin.logError("1st loaded owner: " + firstLoaded.getOwner());
plugin.logError("2nd loaded ID: " + island.getUniqueId());
plugin.logError("2nd loaded owner: " + island.getOwner());
}
if (island.getUniqueId().equals(zEntry.get(island.getMinZ()))) {
return true;
}
return false;
} else {
// Add island
zEntry.put(island.getMinZ(), island);
zEntry.put(island.getMinZ(), island.getUniqueId());
grid.put(island.getMinX(), zEntry);
}
} else {
// Add island
TreeMap<Integer, Island> zEntry = new TreeMap<>();
zEntry.put(island.getMinZ(), island);
TreeMap<Integer, String> zEntry = new TreeMap<>();
zEntry.put(island.getMinZ(), island.getUniqueId());
grid.put(island.getMinX(), zEntry);
}
return true;
@ -76,43 +56,48 @@ class IslandGrid {
* @return true if island existed and was deleted, false if there was nothing to delete
*/
public boolean removeFromGrid(Island island) {
// Remove from grid
if (island != null) {
int x = island.getMinX();
int z = island.getMinZ();
if (grid.containsKey(x)) {
TreeMap<Integer, Island> zEntry = grid.get(x);
if (zEntry.containsKey(z)) {
// Island exists - delete it
zEntry.remove(z);
grid.put(x, zEntry);
return true;
}
}
}
return false;
}
String id = island.getUniqueId();
boolean removed = grid.values().stream()
.anyMatch(innerMap -> innerMap.values().removeIf(innerValue -> innerValue.equals(id)));
grid.values().removeIf(TreeMap::isEmpty);
return removed;
}
/**
* Returns the island at the x,z location or null if there is none.
* This includes the full island space, not just the protected area.
*
* @param x - x coordinate
* @param z - z coordinate
* @return Island or null
*/
* Retrieves the island located at the specified x and z coordinates, covering both the protected area
* and the full island space. Returns null if no island exists at the given location.
*
* @param x the x coordinate of the location
* @param z the z coordinate of the location
* @return the Island at the specified location, or null if no island is found
*/
public Island getIslandAt(int x, int z) {
Entry<Integer, TreeMap<Integer, Island>> en = grid.floorEntry(x);
if (en != null) {
Entry<Integer, Island> ent = en.getValue().floorEntry(z);
if (ent != null) {
// Check if in the island range
Island island = ent.getValue();
if (island.inIslandSpace(x, z)) {
return island;
}
}
// Attempt to find the closest x-coordinate entry that does not exceed 'x'
Entry<Integer, TreeMap<Integer, String>> xEntry = grid.floorEntry(x);
if (xEntry == null) {
return null; // No x-coordinate entry found, return null
}
// Attempt to find the closest z-coordinate entry that does not exceed 'z' within the found x-coordinate
Entry<Integer, String> zEntry = xEntry.getValue().floorEntry(z);
if (zEntry == null) {
return null; // No z-coordinate entry found, return null
}
// Retrieve the island using the id found in the z-coordinate entry
Island island = im.getIslandById(zEntry.getValue());
if (island == null) {
return null; // No island found by the id, return null
}
// Check if the specified coordinates are within the island space
if (island.inIslandSpace(x, z)) {
return island; // Coordinates are within island space, return the island
}
// Coordinates are outside the island space, return null
return null;
}
}

View File

@ -19,6 +19,7 @@ import world.bentobox.bentobox.api.events.island.IslandResetEvent;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.IslandsManager;
/**
* Create and paste a new island
@ -217,7 +218,7 @@ public class NewIsland {
// Register metrics
plugin.getMetrics().ifPresent(BStats::increaseIslandsCreatedCount);
// Save island
plugin.getIslands().save(island);
IslandsManager.updateIsland(island);
}
/**
@ -266,8 +267,6 @@ public class NewIsland {
plugin.getIWM().getAddon(island.getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("")
+ "island.range",
island.getProtectionRange()));
// Save the player so that if the server crashes weird things won't happen
plugin.getPlayers().save(user.getUniqueId());
}
/**

View File

@ -8,6 +8,8 @@ import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
@ -33,15 +35,18 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.material.Colorable;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.BoundingBox;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import io.papermc.lib.PaperLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.hooks.Hook;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.SlimefunHook;
import world.bentobox.bentobox.util.MyBiomeGrid;
import world.bentobox.bentobox.util.Util;
/**
* Regenerates by using a seed world. The seed world is created using the same generator as the game
@ -97,7 +102,10 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
}
final int x = chunkX;
final int z = chunkZ;
newTasks.add(regenerateChunk(di, world, x, z));
// Only add chunks that are generated
if (world.getChunkAt(x, z, false).isGenerated()) {
newTasks.add(regenerateChunk(di, world, x, z));
}
chunkZ++;
if (chunkZ > di.getMaxZChunk()) {
chunkZ = di.getMinZChunk();
@ -119,7 +127,13 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
return regenerateChunk(null, chunk.getWorld(), chunk.getX(), chunk.getZ());
}
private CompletableFuture<Void> regenerateChunk(@Nullable IslandDeletion di, World world, int chunkX, int chunkZ) {
private CompletableFuture<Void> regenerateChunk(@Nullable IslandDeletion di, @NonNull World world, int chunkX,
int chunkZ) {
// Check if chunk has been generated
if (!world.getChunkAt(chunkX, chunkZ, false).isGenerated()) {
return CompletableFuture.completedFuture(null);
}
CompletableFuture<Chunk> seedWorldFuture = getSeedWorldChunk(world, chunkX, chunkZ);
@ -201,10 +215,12 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
// Delete any 3rd party blocks
Location loc = new Location(toChunk.getWorld(), baseX + x, y, baseZ + z);
slimefunHook.ifPresent(hook -> hook.clearBlockInfo(loc, true));
itemsAdderHook.ifPresent(hook -> hook.clearBlockInfo(loc));
}
}
}
// Items Adder
itemsAdderHook.ifPresent(hook -> ItemsAdderHook.deleteAllCustomBlocksInChunk(toChunk));
// Entities
Arrays.stream(fromChunk.getEntities()).forEach(e -> processEntity(e, e.getLocation().toVector().toLocation(toChunk.getWorld())));
@ -333,7 +349,8 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
}
@SuppressWarnings("deprecation")
private CompletableFuture<Void> regenerateChunk(GameModeAddon gm, IslandDeletion di, World world, int chunkX, int chunkZ) {
private CompletableFuture<Void> regenerateChunk(GameModeAddon gm, IslandDeletion di, @Nonnull World world,
int chunkX, int chunkZ) {
CompletableFuture<Chunk> chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ);
CompletableFuture<Void> invFuture = chunkFuture.thenAccept(chunk ->
Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance)
@ -370,6 +387,7 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
double baseZ = chunk.getZ() << 4;
int minHeight = chunk.getWorld().getMinHeight();
int maxHeight = chunk.getWorld().getMaxHeight();
Optional<Hook> slimefunHook = plugin.getHooks().getHook("Slimefun");
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (!limitBox.contains(baseX + x, 0, baseZ + z)) {
@ -383,12 +401,11 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
}
// Delete any 3rd party blocks
Location loc = new Location(chunk.getWorld(), baseX + x, y, baseZ + z);
plugin.getHooks().getHook("Slimefun")
.ifPresent(sf -> ((SlimefunHook) sf).clearBlockInfo(loc, true));
plugin.getHooks().getHook("ItemsAdder")
.ifPresent(hook -> ((ItemsAdderHook) hook).clearBlockInfo(loc));
slimefunHook.ifPresent(sf -> ((SlimefunHook) sf).clearBlockInfo(loc, true));
}
}
}
// Items Adder
plugin.getHooks().getHook("ItemsAdder").ifPresent(hook -> ItemsAdderHook.deleteAllCustomBlocksInChunk(chunk));
}
}

View File

@ -0,0 +1,52 @@
package world.bentobox.bentobox.nms.v1_20_R4;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
import net.minecraft.core.BlockPosition;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.chunk.Chunk;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.nms.PasteHandler;
import world.bentobox.bentobox.util.DefaultPasteUtil;
import world.bentobox.bentobox.util.Util;
public class PasteHandlerImpl implements PasteHandler {
protected static final IBlockData AIR = ((CraftBlockData) AIR_BLOCKDATA).getState();
/**
* Set the block to the location
*
* @param island - island
* @param location - location
* @param bpBlock - blueprint block
*/
@Override
public CompletableFuture<Void> setBlock(Island island, Location location, BlueprintBlock bpBlock) {
return Util.getChunkAtAsync(location).thenRun(() -> {
Block block = location.getBlock();
// Set the block data - default is AIR
BlockData bd = DefaultPasteUtil.createBlockData(bpBlock);
CraftBlockData craft = (CraftBlockData) bd;
net.minecraft.world.level.World nmsWorld = ((CraftWorld) location.getWorld()).getHandle();
Chunk nmsChunk = nmsWorld.d(location.getBlockX() >> 4, location.getBlockZ() >> 4);
BlockPosition bp = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ());
// Setting the block to air before setting to another state prevents some console errors
nmsChunk.a(bp, AIR, false);
nmsChunk.a(bp, craft.getState(), false);
block.setBlockData(bd, false);
DefaultPasteUtil.setBlockState(island, block, bpBlock);
// Set biome
if (bpBlock.getBiome() != null) {
block.setBiome(bpBlock.getBiome());
}
});
}
}

View File

@ -0,0 +1,26 @@
package world.bentobox.bentobox.nms.v1_20_R4;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
import net.minecraft.core.BlockPosition;
import net.minecraft.world.level.World;
import net.minecraft.world.level.chunk.Chunk;
import world.bentobox.bentobox.nms.CopyWorldRegenerator;
public class WorldRegeneratorImpl extends CopyWorldRegenerator {
@Override
public void setBlockInNativeChunk(org.bukkit.Chunk chunk, int x, int y, int z, BlockData blockData,
boolean applyPhysics) {
CraftBlockData craft = (CraftBlockData) blockData;
World nmsWorld = ((CraftWorld) chunk.getWorld()).getHandle();
Chunk nmsChunk = nmsWorld.d(chunk.getX(), chunk.getZ());
BlockPosition bp = new BlockPosition((chunk.getX() << 4) + x, y, (chunk.getZ() << 4) + z);
// Setting the block to air before setting to another state prevents some console errors
nmsChunk.a(bp, PasteHandlerImpl.AIR, applyPhysics);
nmsChunk.a(bp, craft.getState(), applyPhysics);
}
}

View File

@ -77,10 +77,21 @@ public class BlueprintManagementPanel {
environmentToBlueprint = Map.of(World.Environment.NORMAL, normalBlueprint, World.Environment.NETHER, netherBlueprint, World.Environment.THE_END, endBlueprint);
}
/**
* Translate "commands.admin.blueprint.management." + t reference
* @param t - end of reference
* @return translation
*/
private String t(String t) {
return user.getTranslation("commands.admin.blueprint.management." + t);
}
/**
* Translate "commands.admin.blueprint.management." + t + vars reference
* @param t end of reference
* @param vars any other parameters
* @return transmation
*/
private String t(String t, String... vars) {
return user.getTranslation("commands.admin.blueprint.management." + t, vars);
}
@ -186,6 +197,10 @@ public class BlueprintManagementPanel {
// Toggle permission - default is always allowed
pb.item(39, getPermissionIcon(addon, bb));
}
if (plugin.getSettings().getIslandNumber() > 1) {
// Number of times allowed
pb.item(42, getTimesIcon(bb));
}
// Preferred slot
pb.item(40, getSlotIcon(addon, bb));
// Panel has a Back icon.
@ -198,6 +213,25 @@ public class BlueprintManagementPanel {
}
private PanelItem getTimesIcon(BlueprintBundle bb) {
return new PanelItemBuilder().icon(Material.CLOCK).name(t("times"))
.description(bb.getTimes() == 0 ? t("unlimited-times")
: t("maximum-times", TextVariables.NUMBER, String.valueOf(bb.getTimes())))
.clickHandler((panel, u, clickType, slot) -> {
// Left click up, right click down
u.getPlayer().playSound(u.getLocation(), Sound.UI_BUTTON_CLICK, 1F, 1F);
if (clickType == ClickType.LEFT) {
bb.setTimes(bb.getTimes() + 1);
} else if (clickType == ClickType.RIGHT && bb.getTimes() > 0) {
bb.setTimes(bb.getTimes() - 1);
}
// Save
plugin.getBlueprintsManager().saveBlueprintBundle(addon, bb);
panel.getInventory().setItem(42, getTimesIcon(bb).getItem());
return true;
}).build();
}
/**
* Gets the preferred slot icon
* @param addon - addon

View File

@ -7,6 +7,7 @@
package world.bentobox.bentobox.panels.customizable;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
@ -37,6 +38,94 @@ import world.bentobox.bentobox.util.Util;
*/
public class IslandCreationPanel
{
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
/**
* This constant is used for button to indicate that it is Blueprint Bundle type.
*/
private static final String BUNDLES = "BUNDLE";
/**
* This constant is used for button to indicate that it is previous page type.
*/
private static final String PREVIOUS = "PREVIOUS";
/**
* This constant is used for button to indicate that it is next page type.
*/
private static final String NEXT = "NEXT";
/**
* This constant is used for indicating that pages should contain numbering.
*/
private static final String INDEXING = "indexing";
/**
* This constant stores value for SELECT action that is used in panels.
*/
private static final String SELECT_ACTION = "SELECT";
/**
* This constant stores value for COMMAND action that is used in panels.
*/
private static final String COMMANDS_ACTION = "COMMANDS";
/**
* This constant stores value for ERROR message that will be displayed upon failing to run creation commands.
*/
private static final String ISLAND_CREATION_COMMANDS = "ISLAND_CREATION_COMMANDS";
/**
* Button reference
*/
private static final String BUNDLE_BUTTON_REF = "panels.island_creation.buttons.bundle.";
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable allows to access plugin object.
*/
private final BentoBox plugin;
/**
* This variable stores main command that was triggered.
*/
private final CompositeCommand mainCommand;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds world where panel is opened. Without it panel cannot be opened.
*/
private final String mainLabel;
/**
* This variable stores filtered elements.
*/
private final List<BlueprintBundle> elementList;
/**
* This variable holds current pageIndex for multi-page island choosing.
*/
private int pageIndex;
/**
* The world that this command applies to
*/
private final World world;
/**
* true if this panel has been called by a reset command. Changes how the count of used islands is done.
*/
private final boolean reset;
// ---------------------------------------------------------------------
// Section: Constructor
// ---------------------------------------------------------------------
@ -48,20 +137,22 @@ public class IslandCreationPanel
* @param command CompositeCommand object
* @param label The main command label
* @param user User who opens panel
* @param reset
*/
private IslandCreationPanel(@NonNull CompositeCommand command,
@NonNull User user,
@NonNull String label)
@NonNull User user, @NonNull String label, boolean reset)
{
this.plugin = BentoBox.getInstance();
this.user = user;
this.mainLabel = label;
this.world = command.getWorld();
this.reset = reset;
this.elementList = this.plugin.getBlueprintsManager().getBlueprintBundles(command.getAddon()).values().stream().
sorted(Comparator.comparingInt(BlueprintBundle::getSlot).thenComparing(BlueprintBundle::getUniqueId)).
filter(bundle -> !bundle.isRequirePermission() ||
this.user.hasPermission(command.getPermissionPrefix() + "island.create." + bundle.getUniqueId())).
toList();
sorted(Comparator.comparingInt(BlueprintBundle::getSlot).thenComparing(BlueprintBundle::getUniqueId))
.filter(bundle -> !bundle.isRequirePermission() || this.user
.hasPermission(command.getPermissionPrefix() + "island.create." + bundle.getUniqueId()))
.toList();
this.mainCommand = command;
}
@ -83,7 +174,7 @@ public class IslandCreationPanel
{
this.plugin.logError("There are no available phases for selection!");
this.user.sendMessage("no-phases",
TextVariables.GAMEMODE, this.plugin.getDescription().getName());
TextVariables.GAMEMODE, this.plugin.getDescription().getName());
return;
}
@ -126,8 +217,8 @@ public class IslandCreationPanel
private boolean doesCustomPanelExists(GameModeAddon addon, String name)
{
return addon.getDataFolder().exists() &&
new File(addon.getDataFolder(), "panels").exists() &&
new File(addon.getDataFolder(), "panels" + File.separator + name + ".yml").exists();
new File(addon.getDataFolder(), "panels").exists()
&& new File(addon.getDataFolder(), "panels" + File.separator + name + ".yml").exists();
}
@ -149,7 +240,7 @@ public class IslandCreationPanel
int size = this.elementList.size();
if (size <= slot.amountMap().getOrDefault(BUNDLES, 1) ||
1.0 * size / slot.amountMap().getOrDefault(BUNDLES, 1) <= this.pageIndex + 1)
1.0 * size / slot.amountMap().getOrDefault(BUNDLES, 1) <= this.pageIndex + 1)
{
// There are no next elements
return null;
@ -179,7 +270,7 @@ public class IslandCreationPanel
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(),
TextVariables.NUMBER, String.valueOf(nextPageIndex)));
TextVariables.NUMBER, String.valueOf(nextPageIndex)));
}
// Add ClickHandler
@ -187,7 +278,7 @@ public class IslandCreationPanel
{
template.actions().forEach(action -> {
if ((clickType == action.clickType() ||
action.clickType() == ClickType.UNKNOWN) && NEXT.equalsIgnoreCase(action.actionType()))
action.clickType() == ClickType.UNKNOWN) && NEXT.equalsIgnoreCase(action.actionType()))
{
// Next button ignores click type currently.
this.pageIndex++;
@ -202,10 +293,10 @@ public class IslandCreationPanel
// Collect tooltips.
List<String> tooltips = template.actions().stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip()))
.filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty())
@ -259,16 +350,15 @@ public class IslandCreationPanel
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(),
TextVariables.NUMBER, String.valueOf(previousPageIndex)));
TextVariables.NUMBER, String.valueOf(previousPageIndex)));
}
// Add ClickHandler
// Add ClickHandler
builder.clickHandler((panel, user, clickType, i) ->
{
template.actions().forEach(action -> {
if ((clickType == action.clickType() ||
action.clickType() == ClickType.UNKNOWN) && PREVIOUS.equalsIgnoreCase(action.actionType()))
action.clickType() == ClickType.UNKNOWN) && PREVIOUS.equalsIgnoreCase(action.actionType()))
{
// Next button ignores click type currently.
this.pageIndex--;
@ -283,10 +373,10 @@ public class IslandCreationPanel
// Collect tooltips.
List<String> tooltips = template.actions().stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip()))
.filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty())
@ -310,7 +400,7 @@ public class IslandCreationPanel
{
if (this.elementList.isEmpty())
{
// Does not contain any sticks.
// Does not contain any blueprints.
return null;
}
@ -332,9 +422,8 @@ public class IslandCreationPanel
{
// Try to find bundle with requested ID. if not found, use already collected bundle.
blueprintBundle = this.elementList.stream().
filter(bundle -> bundle.getUniqueId().equals(template.dataMap().get("unique_id"))).
findFirst().
orElse(blueprintBundle);
filter(bundle -> bundle.getUniqueId().equals(template.dataMap().get("unique_id"))).findFirst()
.orElse(blueprintBundle);
}
return this.createBundleButton(template, blueprintBundle);
@ -359,8 +448,6 @@ public class IslandCreationPanel
return null;
}
final String reference = "panels.island_creation.buttons.bundle.";
// Get settings for island.
PanelItemBuilder builder = new PanelItemBuilder();
@ -376,72 +463,92 @@ public class IslandCreationPanel
if (template.title() != null)
{
builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title(),
TextVariables.NAME, bundle.getDisplayName()));
TextVariables.NAME, bundle.getDisplayName()));
}
else
{
builder.name(this.user.getTranslation(reference + "name",
TextVariables.NAME, bundle.getDisplayName()));
builder.name(this.user.getTranslation(BUNDLE_BUTTON_REF + "name",
TextVariables.NAME, bundle.getDisplayName()));
}
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(),
TextVariables.DESCRIPTION, String.join("\n", bundle.getDescription())));
TextVariables.DESCRIPTION, String.join("\n", bundle.getDescription())));
}
else
{
builder.description(this.user.getTranslation(reference + "description",
TextVariables.DESCRIPTION, String.join("\n", bundle.getDescription())));
builder.description(this.user.getTranslation(BUNDLE_BUTTON_REF + "description",
TextVariables.DESCRIPTION, String.join("\n", bundle.getDescription())));
}
boolean usedUp = false;
if (plugin.getSettings().getIslandNumber() > 1) {
// Show how many times this bundle can be used
int maxTimes = bundle.getTimes();
if (maxTimes > 0) {
long uses = plugin.getIslands().getIslands(world, user).stream()
.filter(is -> is.getMetaData("bundle")
.map(mdv -> bundle.getDisplayName().equalsIgnoreCase(mdv.asString())
&& !(reset && is.isPrimary(user.getUniqueId()))) // If this is a reset, then ignore the use of the island being reset
.orElse(false))
.count();
builder.description(this.user.getTranslation(BUNDLE_BUTTON_REF + "uses", TextVariables.NUMBER,
String.valueOf(uses), "[max]", String.valueOf(maxTimes)));
if (uses >= maxTimes) {
usedUp = true;
}
} else {
builder.description(this.user.getTranslation(BUNDLE_BUTTON_REF + "unlimited"));
}
}
List<ItemTemplateRecord.ActionRecords> actions = template.actions().stream().
filter(action -> SELECT_ACTION.equalsIgnoreCase(action.actionType()) ||
COMMANDS_ACTION.equalsIgnoreCase(action.actionType())).
toList();
if (usedUp) {
if (plugin.getSettings().isHideUsedBlueprints()) {
// Do not show used up blueprints
return null;
}
} else {
List<ItemTemplateRecord.ActionRecords> actions = template.actions().stream()
.filter(action -> SELECT_ACTION.equalsIgnoreCase(action.actionType())
|| COMMANDS_ACTION.equalsIgnoreCase(action.actionType()))
.toList();
// Add ClickHandler
builder.clickHandler((panel, user, clickType, i) -> {
actions.forEach(action -> {
if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN)
{
if (SELECT_ACTION.equalsIgnoreCase(action.actionType())) {
user.closeInventory();
this.mainCommand.execute(user, this.mainLabel,
Collections.singletonList(bundle.getUniqueId()));
} else if (COMMANDS_ACTION.equalsIgnoreCase(action.actionType())) {
Util.runCommands(user,
Arrays.stream(action.content()
.replaceAll(Pattern.quote(TextVariables.LABEL),
this.mainCommand.getTopLabel())
.split("\n")).toList(),
ISLAND_CREATION_COMMANDS);
}
}
});
// Add ClickHandler
builder.clickHandler((panel, user, clickType, i) ->
{
actions.forEach(action -> {
if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN)
{
if (SELECT_ACTION.equalsIgnoreCase(action.actionType()))
{
user.closeInventory();
this.mainCommand.execute(user, this.mainLabel, Collections.singletonList(bundle.getUniqueId()));
}
else if (COMMANDS_ACTION.equalsIgnoreCase(action.actionType()))
{
Util.runCommands(user,
Arrays.stream(action.content().
replaceAll(Pattern.quote(TextVariables.LABEL), this.mainCommand.getTopLabel()).
split("\n")).
toList(),
ISLAND_CREATION_COMMANDS);
}
}
// Always return true.
return true;
});
// Always return true.
return true;
});
// Collect tooltips.
List<String> tooltips = actions.stream().filter(action -> action.tooltip() != null)
.map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip()))
.filter(text -> !text.isBlank())
.collect(Collectors.toCollection(() -> new ArrayList<>(actions.size())));
// Collect tooltips.
List<String> tooltips = actions.stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(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();
}
@ -458,87 +565,13 @@ public class IslandCreationPanel
* @param command CompositeCommand object
* @param label The main command label
* @param user User who opens panel
* @param reset true if this is an island reset
*/
public static void openPanel(@NonNull CompositeCommand command,
@NonNull User user,
@NonNull String label)
@NonNull User user, @NonNull String label, boolean reset)
{
new IslandCreationPanel(command, user, label).build();
new IslandCreationPanel(command, user, label, reset).build();
}
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
/**
* This constant is used for button to indicate that it is Blueprint Bundle type.
*/
private static final String BUNDLES = "BUNDLE";
/**
* This constant is used for button to indicate that it is previous page type.
*/
private static final String PREVIOUS = "PREVIOUS";
/**
* This constant is used for button to indicate that it is next page type.
*/
private static final String NEXT = "NEXT";
/**
* This constant is used for indicating that pages should contain numbering.
*/
private static final String INDEXING = "indexing";
/**
* This constant stores value for SELECT action that is used in panels.
*/
private static final String SELECT_ACTION = "SELECT";
/**
* This constant stores value for COMMAND action that is used in panels.
*/
private static final String COMMANDS_ACTION = "COMMANDS";
/**
* This constant stores value for ERROR message that will be displayed upon failing to run creation commands.
*/
private static final String ISLAND_CREATION_COMMANDS = "ISLAND_CREATION_COMMANDS";
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable allows to access plugin object.
*/
private final BentoBox plugin;
/**
* This variable stores main command that was triggered.
*/
private final CompositeCommand mainCommand;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds world where panel is opened. Without it panel cannot be opened.
*/
private final String mainLabel;
/**
* This variable stores filtered elements.
*/
private final List<BlueprintBundle> elementList;
/**
* This variable holds current pageIndex for multi-page island choosing.
*/
private int pageIndex;
}

View File

@ -4,6 +4,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
@ -16,6 +17,7 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.flags.Flag.Mode;
import world.bentobox.bentobox.api.flags.Flag.Type;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
@ -46,16 +48,7 @@ public class SettingsTab implements Tab, ClickHandler {
protected Island island;
protected TabbedPanel parent;
/**
* Show a tab of settings
* @param user - user who is viewing the tab
* @param type - flag type
*/
public SettingsTab(User user, Type type) {
this.user = user;
this.type = type;
// Island and world are set when the parent is set.
}
private Map<UUID, Flag.Mode> currentMode = new HashMap<>();
/**
* Show a tab of settings
@ -69,6 +62,21 @@ public class SettingsTab implements Tab, ClickHandler {
this.type = type;
}
/**
* Show a tab of settings
* @param world - world
* @param user - user who is viewing the tab
* @param type - flag type
* @param defaultMode - the default mode to show
* @since 2.4.0
*/
public SettingsTab(World world, User user, Type type, Flag.Mode defaultMode) {
this.world = world;
this.user = user;
this.type = type;
currentMode.put(user.getUniqueId(), defaultMode);
}
/**
* @return list of flags that will be shown in this panel
*/
@ -81,7 +89,7 @@ public class SettingsTab implements Tab, ClickHandler {
// Remove any that are not for this game mode
plugin.getIWM().getAddon(world).ifPresent(gm -> flags.removeIf(f -> !f.getGameModes().isEmpty() && !f.getGameModes().contains(gm)));
// Remove any that are the wrong rank or that will be on the top row
Flag.Mode mode = plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId());
Flag.Mode mode = currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC);
plugin.getIWM().getAddon(world).ifPresent(gm -> flags.removeIf(f -> f.getMode().isGreaterThan(mode) ||
f.getMode().equals(Flag.Mode.TOP_ROW)));
return flags;
@ -120,13 +128,14 @@ public class SettingsTab implements Tab, ClickHandler {
int i = 0;
// Jump past empty tabs
while (flags.isEmpty() && i++ < Flag.Mode.values().length) {
plugin.getPlayers().setFlagsDisplayMode(user.getUniqueId(), plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId()).getNext());
currentMode.put(user.getUniqueId(), currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC).getNext());
flags = getFlags();
}
return flags.stream().map(
List<@Nullable PanelItem> result = flags.stream().map(
(f -> f.toPanelItem(plugin, user, world, island,
plugin.getIWM().getHiddenFlags(world).contains(f.getID()))))
.toList();
return result;
}
@Override
@ -137,8 +146,9 @@ public class SettingsTab implements Tab, ClickHandler {
icons.put(4, Flags.CHANGE_SETTINGS.toPanelItem(plugin, user, world, island, false));
icons.put(5, Flags.LOCK.toPanelItem(plugin, user, world, island, false));
}
// Add the mode icon
switch (plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId())) {
switch (currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC)) {
case ADVANCED -> icons.put(7, new PanelItemBuilder().icon(Material.GOLD_INGOT)
.name(user.getTranslation(PROTECTION_PANEL + "mode.advanced.name"))
.description(user.getTranslation(PROTECTION_PANEL + "mode.advanced.description"), "",
@ -161,7 +171,8 @@ public class SettingsTab implements Tab, ClickHandler {
.clickHandler(this)
.build());
}
// Add the reset everything to default - it's only in the player's settings panel
// Add the reset everything to default - it's only in the player's settings panel
if (island != null && user.getUniqueId().equals(island.getOwner())) {
icons.put(8, new PanelItemBuilder().icon(Material.TNT)
.name(user.getTranslation(PROTECTION_PANEL + "reset-to-default.name"))
@ -216,7 +227,7 @@ public class SettingsTab implements Tab, ClickHandler {
@Override
public boolean onClick(Panel panel, User user, ClickType clickType, int slot) {
// Cycle the mode
plugin.getPlayers().setFlagsDisplayMode(user.getUniqueId(), plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId()).getNext());
currentMode.put(user.getUniqueId(), currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC).getNext());
if (panel instanceof TabbedPanel tp) {
tp.setActivePage(0);
tp.refreshPanel();

View File

@ -7,6 +7,7 @@ import org.bukkit.Material;
import org.bukkit.World;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag.Type;
import world.bentobox.bentobox.api.flags.clicklisteners.WorldToggleClick;
import world.bentobox.bentobox.api.localization.TextVariables;

View File

@ -113,6 +113,10 @@ public class IslandInfo {
if (island.getPurgeProtected()) {
user.sendMessage("commands.admin.info.purge-protected");
}
// Show bundle info if available
island.getMetaData("bundle").ifPresent(mdv -> {
user.sendMessage("commands.admin.info.bundle", TextVariables.NAME, mdv.asString());
});
// Fire info event to allow other addons to add to info
IslandEvent.builder().island(island).location(island.getCenter()).reason(IslandEvent.Reason.INFO)
.involvedPlayer(user.getUniqueId()).addon(addon).admin(true).build();

View File

@ -261,7 +261,8 @@ public class ItemParser {
boolean isUpgraded = !part[2].isEmpty() && !part[2].equalsIgnoreCase("1");
boolean isExtended = part[3].equalsIgnoreCase("EXTENDED");
PotionData data = new PotionData(type, isExtended, isUpgraded);
potionMeta.setBasePotionData(data);
// TODO: Set extended and u[graded settings.
potionMeta.setBasePotionType(type);
result.setItemMeta(potionMeta);
result.setAmount(Integer.parseInt(part[5]));
return result;
@ -285,7 +286,7 @@ public class ItemParser {
private static ItemStack parsePotion(String[] part) {
if (part.length == 6) {
BentoBox.getInstance().logWarning("The old potion parsing detected for " + part[0] +
". Please update your configs, as SPIGOT changed potion types.");
". Please update your configs, as SPIGOT changed potion types.");
return parsePotionOld(part);
}
@ -314,7 +315,7 @@ public class ItemParser {
if (result.getItemMeta() instanceof PotionMeta meta) {
PotionType potionType = Enums.getIfPresent(PotionType.class, part[1].toUpperCase(Locale.ENGLISH)).
or(PotionType.WATER);
or(PotionType.WATER);
meta.setBasePotionType(potionType);
result.setItemMeta(meta);
}
@ -340,7 +341,18 @@ public class ItemParser {
BannerMeta meta = (BannerMeta) result.getItemMeta();
if (meta != null) {
for (int i = 2; i < part.length; i += 2) {
meta.addPattern(new Pattern(DyeColor.valueOf(part[i + 1]), PatternType.valueOf(part[i])));
PatternType pt = Enums.getIfPresent(PatternType.class, part[i]).orNull();
if (pt == null) {
// Try to convert old to new
if (part[i].trim().equals("STRIPE_SMALL")
&& Enums.getIfPresent(PatternType.class, "SMALL_STRIPES").isPresent()) {
pt = PatternType.SMALL_STRIPES;
}
}
DyeColor dc = Enums.getIfPresent(DyeColor.class, part[i + 1]).orNull();
if (pt != null && dc != null) {
meta.addPattern(new Pattern(dc, pt));
}
}
result.setItemMeta(meta);
}

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.util;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@ -42,6 +43,9 @@ import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.base.Enums;
import com.google.common.base.Optional;
import io.papermc.lib.PaperLib;
import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult;
import world.bentobox.bentobox.BentoBox;
@ -744,6 +748,7 @@ public class Util {
String serverPackageName = Bukkit.getServer().getClass().getPackage().getName();
String pluginPackageName = plugin.getClass().getPackage().getName();
String version = serverPackageName.substring(serverPackageName.lastIndexOf('.') + 1);
BentoBox.getInstance().log("Optimizing for " + version);
PasteHandler handler;
try {
Class<?> clazz = Class.forName(pluginPackageName + ".nms." + version + ".PasteHandlerImpl");
@ -801,4 +806,30 @@ public class Util {
Util.translateColorCodes(input.replaceAll("[\\\\/:*?\"<>|\s]", "_"))).
toLowerCase();
}
/**
* Attempts to find the first matching enum constant from an array of possible string representations.
* This method sequentially checks each string against the enum constants of the specified enum class
* by normalizing the string values to uppercase before comparison, enhancing the likelihood of a match
* if the enum constants are defined in uppercase.
*
* @param enumClass the Class object of the enum type to be checked against
* @param values an array of string values which are potential matches for the enum constants
* @param <T> the type parameter of the enum
* @return the first matching enum constant if a match is found; otherwise, returns null
* @throws IOException
* @throws NullPointerException if either {@code enumClass} or {@code values} are null
*/
public static <T extends Enum<T>> T findFirstMatchingEnum(Class<T> enumClass, String... values) {
if (enumClass == null || values == null) {
return null;
}
for (String value : values) {
Optional<T> enumConstant = Enums.getIfPresent(enumClass, value.toUpperCase());
if (enumConstant.isPresent()) {
return enumConstant.get();
}
}
return null; // Return null if no match is found
}
}

View File

@ -231,7 +231,15 @@ public class ServerCompatibility {
/**
* @since 2.0.0
*/
V1_20_4(Compatibility.COMPATIBLE);
V1_20_4(Compatibility.COMPATIBLE),
/**
* @since 2.4.0
*/
V1_20_5(Compatibility.COMPATIBLE),
/**
* @since 2.4.0
*/
V1_20_6(Compatibility.COMPATIBLE);
private final Compatibility compatibility;

View File

@ -166,7 +166,7 @@ commands:
&a Green Particles &f show the default protected range if the island protection range differs from it.
showing: '&2 Showing range indicators'
set:
parameters: <player> <range>
parameters: <player> <range> [island location]
description: sets the island protected range
success: '&a Set island protection range to &b [number]&a .'
reset:
@ -175,12 +175,12 @@ commands:
success: '&a Reset island protection range to &b [number]&a .'
add:
description: increases the island protected range
parameters: <player> <range>
parameters: <player> <range> [island location]
success: '&a Successfully increased &b [name]&a ''s island protected range
to &b [total] &7 (&b +[number]&7 )&a .'
remove:
description: decreases the island protected range
parameters: <player> <range>
parameters: <player> <range> [island location]
success: '&a Successfully decreased &b [name]&a ''s island protected range
to &b [total] &7 (&b -[number]&7 )&a .'
register:
@ -230,6 +230,7 @@ commands:
banned-players: 'Banned players:'
banned-format: '&c [name]'
unowned: '&c Unowned'
bundle: '&a Blueprint Bundle used to create island: &b [name]'
switch:
description: switch on/off protection bypass
op: '&c Ops can always bypass protection. Deop to use command.'
@ -414,6 +415,12 @@ commands:
slot-instructions: |
&a Left click to increment
&a Right click to decrement
times: |
&a Max concurrent uses by player
&a Left click to increment
&a Right click to decrement
unlimited-times: &a Unlimited
maximum-times: &a Max [number] times
resetflags:
parameters: '[flag]'
description: Reset all islands to default flag settings in config.yml
@ -547,6 +554,7 @@ commands:
an administrator.'
creating-island: '&a Finding a spot for your island...'
you-cannot-make: '&c You cannot make any more islands!'
max-uses: "&c You cannot make any more of that type of island!"
you-cannot-make-team: '&c Team members cannot make islands in the same world as their team island.'
pasting:
estimated-time: '&a Estimated time: &b [number] &a seconds.'
@ -678,8 +686,10 @@ commands:
already-has-rank: '&c Player already has a rank!'
you-are-a-coop-member: '&2 You were cooped by &b[name]&a.'
success: '&a You cooped &b [name]&a.'
name-has-invited-you: '&a [name] has invited you to join as a coop member
of their island.'
name-has-invited-you: |
&a [name] has invited you
&a to join as a coop member
&a of their island.
uncoop:
description: remove a coop rank from player
parameters: <player>
@ -696,8 +706,10 @@ commands:
description: give a player trusted rank on your island
parameters: <player>
trust-in-yourself: '&c Trust in yourself!'
name-has-invited-you: '&a [name] has invited you to join as a trusted member
of their island.'
name-has-invited-you: |
&a [name] has invited you
&a to join as a trusted member
&a of their island.
player-already-trusted: '&c Player is already trusted!'
you-are-trusted: '&2 You are trusted by &b [name]&a !'
success: '&a You trusted &b [name]&a .'
@ -714,10 +726,14 @@ commands:
description: invite a player to join your island
invitation-sent: '&a Invitation sent to &b[name]&a.'
removing-invite: '&c Removing invite.'
name-has-invited-you: '&a [name] has invited you to join their island.'
name-has-invited-you: |
&a [name] has invited you
&a to join their island.
to-accept-or-reject: '&a Do /[label] team accept to accept, or /[label] team
reject to reject'
you-will-lose-your-island: '&c WARNING! You will lose all your islands if you accept!'
you-will-lose-your-island: |
&c WARNING! You will lose all
&c your islands if you accept!
gui:
titles:
team-invite-panel: "Invite Players"
@ -758,9 +774,10 @@ commands:
you-joined-island: '&a You joined an island! Use &b/[label] team &a to see
the other members.'
name-joined-your-island: '&a [name] joined your island!'
confirmation: |-
&c Are you sure you want to accept this invite?
&c&l You will &n LOSE ALL &r&c&l your islands!
confirmation: |
&c Are you sure you
&c want to accept this
&c invite?
reject:
description: reject an invitation
you-rejected-invite: '&a You rejected the invitation to join an island.'
@ -1811,6 +1828,8 @@ panels:
name: "&l [name]"
description: |-
[description]
uses: "&a Used [number]/[max]"
unlimited: "&a Unlimited uses allowed"
# The section of translations used in Language Panel
language:
title: "&2&l Select your language"

View File

@ -36,6 +36,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@ -50,11 +52,12 @@ import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ BentoBox.class, Flags.class, Util.class, Bukkit.class})
@PrepareForTest({ BentoBox.class, Flags.class, Util.class, Bukkit.class, IslandsManager.class })
public class TestBentoBox extends AbstractCommonSetup {
private static final UUID MEMBER_UUID = UUID.randomUUID();
private static final UUID VISITOR_UUID = UUID.randomUUID();
@ -74,6 +77,9 @@ public class TestBentoBox extends AbstractCommonSetup {
public void setUp() throws Exception {
super.setUp();
// IslandsManager static
PowerMockito.mockStatic(IslandsManager.class, Mockito.RETURNS_MOCKS);
when(plugin.getCommandsManager()).thenReturn(cm);
SkullMeta skullMeta = mock(SkullMeta.class);
@ -85,6 +91,7 @@ public class TestBentoBox extends AbstractCommonSetup {
when(player.hasPermission(anyString())).thenReturn(true);
when(location.getWorld()).thenReturn(world);
when(ownerOfIsland.getLocation()).thenReturn(location);
when(visitorToIsland.getLocation()).thenReturn(location);
when(location.clone()).thenReturn(location);
@ -93,8 +100,13 @@ public class TestBentoBox extends AbstractCommonSetup {
when(ownerOfIsland.getUniqueId()).thenReturn(uuid);
when(visitorToIsland.getUniqueId()).thenReturn(VISITOR_UUID);
// Util
PowerMockito.mockStatic(Util.class);
when(Util.findFirstMatchingEnum(any(), any())).thenCallRealMethod();
island.setOwner(uuid);
island.setProtectionRange(100);
island.setCenter(location);
HashMap<UUID, Integer> members = new HashMap<>();
members.put(uuid, RanksManager.OWNER_RANK);
members.put(MEMBER_UUID, RanksManager.MEMBER_RANK);

View File

@ -32,10 +32,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
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 com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonDescriptionException;
import world.bentobox.bentobox.managers.AddonsManager;
@ -46,7 +49,7 @@ import world.bentobox.bentobox.managers.AddonsManager;
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest( { BentoBox.class, Bukkit.class })
@PrepareForTest({ BentoBox.class, Bukkit.class, MultiLib.class })
public class AddonClassLoaderTest {
private enum mandatoryTags {
@ -80,6 +83,7 @@ public class AddonClassLoaderTest {
*/
@Before
public void setUp() throws Exception {
PowerMockito.mockStatic(MultiLib.class, Mockito.RETURNS_MOCKS);
// Set up plugin
plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);

View File

@ -44,13 +44,15 @@ import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
@RunWith(PowerMockRunner.class)
@PrepareForTest( { BentoBox.class, Bukkit.class })
@PrepareForTest({ BentoBox.class, Bukkit.class, MultiLib.class })
public class AddonTest {
public static int BUFFER_SIZE = 10240;
@ -90,6 +92,8 @@ public class AddonTest {
// Addons manager
when(plugin.getAddonsManager()).thenReturn(am);
// MultiLib
PowerMockito.mockStatic(MultiLib.class, Mockito.RETURNS_MOCKS);
// Mock item factory (for itemstacks)
ItemFactory itemFactory = mock(ItemFactory.class);

Some files were not shown because too many files have changed in this diff Show More