Implements an island reservation system using the admin register command (#850)

* Implements an island reservation system using the admin register command

Admin flies to an empty spot and registers the player there. This
creates a bedrock block to mark the spot but it sets the island as
reserved for the target player. The next time a player issues the island
command (or island create) they get the selection of islands and it is
pasted at that location.

https://github.com/BentoBoxWorld/BentoBox/issues/749

* Update src/main/java/world/bentobox/bentobox/database/objects/Island.java

Co-Authored-By: Florian CUNY <poslovitch@bentobox.world>

* Update src/main/java/world/bentobox/bentobox/database/objects/Island.java

Co-Authored-By: Florian CUNY <poslovitch@bentobox.world>

* Update src/main/java/world/bentobox/bentobox/database/objects/Island.java

Co-Authored-By: Florian CUNY <poslovitch@bentobox.world>

* Update src/main/java/world/bentobox/bentobox/database/objects/Island.java

Co-Authored-By: Florian CUNY <poslovitch@bentobox.world>
This commit is contained in:
tastybento 2019-07-21 15:36:14 -07:00 committed by Florian CUNY
parent 9291f02c04
commit 6926ecbb9e
10 changed files with 508 additions and 34 deletions

View File

@ -82,7 +82,7 @@ public class AdminRegisterCommand extends ConfirmableCommand {
Bukkit.getServer().getPluginManager().callEvent(event);
return true;
}).orElse(false)) {
// Island does not exist
// Island does not exist - this is a reservation
user.sendMessage("commands.admin.register.no-island-here");
this.askConfirmation(user, () -> {
// Make island here
@ -92,12 +92,13 @@ public class AdminRegisterCommand extends ConfirmableCommand {
return;
}
getIslands().setOwner(user, targetUUID, i);
getWorld().getBlockAt(i.getCenter()).setType(Material.BEDROCK);
user.sendMessage("commands.admin.register.registered-island", "[xyz]", Util.xyz(i.getCenter().toVector()));
i.setReserved(true);
i.getCenter().getBlock().setType(Material.BEDROCK);
user.sendMessage("commands.admin.register.reserved-island", "[xyz]", Util.xyz(i.getCenter().toVector()));
IslandBaseEvent event = IslandEvent.builder()
.island(i)
.location(i.getCenter())
.reason(IslandEvent.Reason.CREATED)
.reason(IslandEvent.Reason.RESERVED)
.involvedPlayer(targetUUID)
.admin(true)
.build();

View File

@ -7,6 +7,7 @@ import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
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.island.NewIsland;
import world.bentobox.bentobox.panels.IslandCreationPanel;
@ -18,6 +19,8 @@ import world.bentobox.bentobox.panels.IslandCreationPanel;
*/
public class IslandCreateCommand extends CompositeCommand {
private Island island;
/**
* Command to create an island
* @param islandCommand - parent command
@ -36,8 +39,14 @@ public class IslandCreateCommand extends CompositeCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (getIslands().hasIsland(getWorld(), user.getUniqueId())
|| getIslands().inTeam(getWorld(), user.getUniqueId())) {
// Check if the island is reserved
island = getIslands().getIsland(getWorld(), user);
if (island != null) {
// Reserved islands can be made
if (island.isReserved()) {
return true;
}
// You cannot make an island
user.sendMessage("general.errors.already-have-island");
return false;
}

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.api.commands.island;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang.math.NumberUtils;
@ -7,6 +8,7 @@ import org.apache.commons.lang.math.NumberUtils;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.DelayedTeleportCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
/**
@ -27,17 +29,28 @@ public class IslandGoCommand extends DelayedTeleportCommand {
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (getIslands().getIsland(getWorld(), user.getUniqueId()) == null) {
public boolean canExecute(User user, String label, List<String> args) {
// Check if the island is reserved
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island == null) {
user.sendMessage("general.errors.no-island");
return false;
}
if (island.isReserved()) {
// Send player to create and island
return getParent().getSubCommand("create").map(createCmd -> createCmd.call(user, createCmd.getLabel(), Collections.emptyList())).orElse(false);
}
if ((getIWM().inWorld(user.getWorld()) && Flags.PREVENT_TELEPORT_WHEN_FALLING.isSetForWorld(user.getWorld()))
&& user.getPlayer().getFallDistance() > 0) {
// We're sending the "hint" to the player to tell them they cannot teleport while falling.
user.sendMessage(Flags.PREVENT_TELEPORT_WHEN_FALLING.getHintReference());
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (!args.isEmpty() && NumberUtils.isDigits(args.get(0))) {
int homeValue = Integer.parseInt(args.get(0));
int maxHomes = user.getPermissionValue(getPermissionPrefix() + "island.maxhomes", getIWM().getMaxHomes(getWorld()));

View File

@ -124,7 +124,12 @@ public class IslandEvent extends IslandBaseEvent {
* Player was expelled
* @since 1.4.0
*/
EXPEL
EXPEL,
/**
* The island was reserved and now is being pasted.
* @since 1.6.0
*/
RESERVED
}
public static IslandEventBuilder builder() {

View File

@ -161,6 +161,14 @@ public class Island implements DataObject {
@Expose
private Map<String, Integer> commandRanks;
/**
* If true then this space is reserved for the owner and when they teleport there they will be asked to make an island
* @since 1.6.0
*/
@Expose
@Nullable
private Boolean reserved = null;
/*
* *************************** Constructors ******************************
*/
@ -1148,6 +1156,24 @@ public class Island implements DataObject {
this.commandRanks.put(command, rank);
}
/**
* Returns whether this Island is currently reserved or not.
* If {@code true}, this means no blocks, except a bedrock one at the center of the island, exist.
* @return {@code true} if this Island is reserved, {@code false} otherwise.
* @since 1.6.0
*/
public boolean isReserved() {
return reserved == null ? false : reserved;
}
/**
* @param reserved the reserved to set
* @since 1.6.0
*/
public void setReserved(boolean reserved) {
this.reserved = reserved;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@ -1161,5 +1187,4 @@ public class Island implements DataObject {
+ ", levelHandicap=" + levelHandicap + ", spawnPoint=" + spawnPoint + ", doNotLoad=" + doNotLoad + "]";
}
}

View File

@ -552,7 +552,7 @@ public class IslandsManager {
}
/**
* Checks if a player has an island in the world
* Checks if a player has an island in the world and owns it
* @param world - world to check
* @param user - the user
* @return true if player has island and owns it

View File

@ -158,16 +158,32 @@ public class NewIsland {
* @param oldIsland old island that is being replaced, if any
*/
public void newIsland(Island oldIsland) {
Location next = getNextIsland();
if (next == null) {
plugin.logError("Failed to make island - no unoccupied spot found");
return;
Location next = null;
if (plugin.getIslands().hasIsland(world, user)) {
// Island exists, it just needs pasting
island = plugin.getIslands().getIsland(world, user);
if (island != null && island.isReserved()) {
next = island.getCenter();
// Clear the reservation
island.setReserved(false);
} else {
// This should never happen unless we allow another way to paste over islands without reserving
plugin.logError("New island for user " + user.getName() + " was not reserved!");
}
}
// Add to the grid
island = plugin.getIslands().createIsland(next, user.getUniqueId());
if (island == null) {
plugin.logError("Failed to make island! Island could not be added to the grid.");
return;
// If the reservation fails, then we need to make a new island anyway
if (next == null) {
next = getNextIsland();
if (next == null) {
plugin.logError("Failed to make island - no unoccupied spot found");
return;
}
// Add to the grid
island = plugin.getIslands().createIsland(next, user.getUniqueId());
if (island == null) {
plugin.logError("Failed to make island! Island could not be added to the grid.");
return;
}
}
// Clear any old home locations (they should be clear, but just in case)
@ -258,7 +274,9 @@ public class NewIsland {
}
// Set default settings
island.setFlagsDefaults();
plugin.getMetrics().ifPresent(BStats::increaseIslandsCreatedCount);
if (!reason.equals(Reason.RESERVED)) {
plugin.getMetrics().ifPresent(BStats::increaseIslandsCreatedCount);
}
// Save island
plugin.getIslands().save(island);
}

View File

@ -134,6 +134,7 @@ commands:
parameters: "<player>"
description: "register player to unowned island you are on"
registered-island: "&aRegistered player to island at [xyz]."
reserved-island: "&aReserved island at [xyz] for player."
already-owned: "&cIsland is already owned by another player!"
no-island-here: "&cThere is no island here. Confirm to make one."
in-deletion: "&cThis island space is currently being deleted. Try later."

View File

@ -1,6 +1,3 @@
/**
*
*/
package world.bentobox.bentobox.api.commands.island;
import static org.junit.Assert.assertEquals;
@ -23,6 +20,7 @@ import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitScheduler;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -126,6 +124,7 @@ public class IslandCreateCommandTest {
when(im.isOwner(any(), eq(uuid))).thenReturn(false);
// Has team
when(im.inTeam(any(), eq(uuid))).thenReturn(true);
when(plugin.getIslands()).thenReturn(im);
@ -194,7 +193,9 @@ public class IslandCreateCommandTest {
*/
@Test
public void testCanExecuteUserStringListOfStringHasIsland() {
when(im.hasIsland(any(), Mockito.any(UUID.class))).thenReturn(true);
@Nullable
Island island = mock(Island.class);
when(im.getIsland(any(), Mockito.any(User.class))).thenReturn(island);
assertFalse(cc.canExecute(user, "", Collections.emptyList()));
verify(user).sendMessage(eq("general.errors.already-have-island"));
}
@ -203,11 +204,13 @@ public class IslandCreateCommandTest {
* Test method for {@link world.bentobox.bentobox.api.commands.island.IslandCreateCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteUserStringListOfStringInTeam() {
when(im.hasIsland(any(), Mockito.any(UUID.class))).thenReturn(false);
when(im.inTeam(any(), Mockito.any(UUID.class))).thenReturn(true);
assertFalse(cc.canExecute(user, "", Collections.emptyList()));
verify(user).sendMessage(eq("general.errors.already-have-island"));
public void testCanExecuteUserStringListOfStringHasIslandReserved() {
@Nullable
Island island = mock(Island.class);
when(im.getIsland(any(), Mockito.any(User.class))).thenReturn(island);
when(island.isReserved()).thenReturn(true);
assertTrue(cc.canExecute(user, "", Collections.emptyList()));
verify(user, never()).sendMessage(eq("general.errors.already-have-island"));
}

View File

@ -13,16 +13,24 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Difficulty;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -38,9 +46,12 @@ import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.user.Notifier;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
@ -75,6 +86,9 @@ public class IslandGoCommandTest {
private IslandGoCommand igc;
@Mock
private Notifier notifier;
@Mock
private World world;
private @Nullable WorldSettings ws;
/**
* @throws java.lang.Exception
@ -97,6 +111,7 @@ public class IslandGoCommandTest {
UUID uuid = UUID.randomUUID();
when(player.getUniqueId()).thenReturn(uuid);
when(player.getName()).thenReturn("tastybento");
when(player.getWorld()).thenReturn(world);
user = User.getInstance(player);
// Set the User class plugin as this one
User.setPlugin(plugin);
@ -105,6 +120,9 @@ public class IslandGoCommandTest {
// Parent command has no aliases
when(ic.getSubCommandAliases()).thenReturn(new HashMap<>());
when(ic.getTopLabel()).thenReturn("island");
// Have the create command point to the ic command
Optional<CompositeCommand> createCommand = Optional.of(ic);
when(ic.getSubCommand(eq("create"))).thenReturn(createCommand);
// No island for player to begin with (set it later in the tests)
when(im.hasIsland(any(), eq(uuid))).thenReturn(false);
@ -133,6 +151,11 @@ public class IslandGoCommandTest {
IslandWorldManager iwm = mock(IslandWorldManager.class);
when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
when(plugin.getIWM()).thenReturn(iwm);
when(iwm.inWorld(any(World.class))).thenReturn(true);
ws = new MyWorldSettings();
when(iwm.getWorldSettings(any())).thenReturn(ws);
// Just return an empty addon for now
when(iwm.getAddon(any())).thenReturn(Optional.empty());
PowerMockito.mockStatic(Util.class);
@ -159,22 +182,68 @@ public class IslandGoCommandTest {
}
/**
* Test method for {@link IslandGoCommand#execute(User, String, List)}
* Test method for {@link IslandGoCommand#canExecute(User, String, List)}
*/
@Test
public void testExecuteNoArgsNoIsland() {
when(im.getIsland(any(), any(UUID.class))).thenReturn(null);
assertFalse(igc.execute(user, igc.getLabel(), Collections.emptyList()));
assertFalse(igc.canExecute(user, igc.getLabel(), Collections.emptyList()));
verify(player).sendMessage("general.errors.no-island");
}
/**
* Test method for {@link IslandGoCommand#execute(User, String, List)}
* Test method for {@link IslandGoCommand#canExecute(User, String, List)}
*/
@Test
public void testExecuteNoArgs() {
when(im.getIsland(any(), any(UUID.class))).thenReturn(island);
assertTrue(igc.execute(user, igc.getLabel(), Collections.emptyList()));
assertTrue(igc.canExecute(user, igc.getLabel(), Collections.emptyList()));
}
/**
* Test method for {@link IslandGoCommand#canExecute(User, String, List)}
*/
@Test
public void testExecuteNoArgsReservedIsland() {
when(im.getIsland(any(), any(UUID.class))).thenReturn(island);
when(ic.call(any(), any(), any())).thenReturn(true);
when(island.isReserved()).thenReturn(true);
assertTrue(igc.canExecute(user, igc.getLabel(), Collections.emptyList()));
verify(ic).call(any(), any(), any());
}
/**
* Test method for {@link IslandGoCommand#canExecute(User, String, List)}
*/
@Test
public void testExecuteNoArgsReservedIslandNoCreateCommand() {
when(ic.getSubCommand(eq("create"))).thenReturn(Optional.empty());
when(im.getIsland(any(), any(UUID.class))).thenReturn(island);
when(ic.call(any(), any(), any())).thenReturn(true);
when(island.isReserved()).thenReturn(true);
assertFalse(igc.canExecute(user, igc.getLabel(), Collections.emptyList()));
verify(ic, Mockito.never()).call(any(), any(), any());
}
/**
* Test method for {@link IslandGoCommand#canExecute(User, String, List)}
*/
@Test
public void testExecuteNoArgsNoTeleportWhenFalling() {
Flags.PREVENT_TELEPORT_WHEN_FALLING.setSetting(world, true);
when(player.getFallDistance()).thenReturn(10F);
assertFalse(igc.canExecute(user, igc.getLabel(), Collections.emptyList()));
verify(player).sendMessage(eq("protection.flags.PREVENT_TELEPORT_WHEN_FALLING.hint"));
}
/**
* Test method for {@link IslandGoCommand#canExecute(User, String, List)}
*/
@Test
public void testExecuteNoArgsNoTeleportWhenFallingNotFalling() {
Flags.PREVENT_TELEPORT_WHEN_FALLING.setSetting(world, true);
when(player.getFallDistance()).thenReturn(0F);
assertTrue(igc.canExecute(user, igc.getLabel(), Collections.emptyList()));
}
/**
@ -303,4 +372,334 @@ public class IslandGoCommandTest {
igc.onPlayerMove(e);
verify(notifier).notify(any(), eq("commands.delay.moved-so-command-cancelled"));
}
class MyWorldSettings implements WorldSettings {
private Map<String, Boolean> worldFlags = new HashMap<>();
/**
* @param worldFlags the worldFlags to set
*/
public void setWorldFlags(Map<String, Boolean> worldFlags) {
this.worldFlags = worldFlags;
}
@Override
public GameMode getDefaultGameMode() {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<Flag, Integer> getDefaultIslandFlags() {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<Flag, Integer> getDefaultIslandSettings() {
// TODO Auto-generated method stub
return null;
}
@Override
public Difficulty getDifficulty() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setDifficulty(Difficulty difficulty) {
// TODO Auto-generated method stub
}
@Override
public String getFriendlyName() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getIslandDistance() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getIslandHeight() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getIslandProtectionRange() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getIslandStartX() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getIslandStartZ() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getIslandXOffset() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getIslandZOffset() {
// TODO Auto-generated method stub
return 0;
}
@Override
public List<String> getIvSettings() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getMaxHomes() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getMaxIslands() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getMaxTeamSize() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getNetherSpawnRadius() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getPermissionPrefix() {
// TODO Auto-generated method stub
return null;
}
@Override
public Set<EntityType> getRemoveMobsWhitelist() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getSeaHeight() {
// TODO Auto-generated method stub
return 0;
}
@Override
public List<String> getHiddenFlags() {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getVisitorBannedCommands() {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, Boolean> getWorldFlags() {
return worldFlags;
}
@Override
public String getWorldName() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isDragonSpawn() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEndGenerate() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEndIslands() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isNetherGenerate() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isNetherIslands() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isOnJoinResetEnderChest() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isOnJoinResetInventory() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isOnJoinResetMoney() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isOnLeaveResetEnderChest() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isOnLeaveResetInventory() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isOnLeaveResetMoney() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isUseOwnGenerator() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isWaterUnsafe() {
// TODO Auto-generated method stub
return false;
}
@Override
public List<String> getGeoLimitSettings() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getResetLimit() {
// TODO Auto-generated method stub
return 0;
}
@Override
public long getResetEpoch() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setResetEpoch(long timestamp) {
// TODO Auto-generated method stub
}
@Override
public boolean isTeamJoinDeathReset() {
// TODO Auto-generated method stub
return false;
}
@Override
public int getDeathsMax() {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean isDeathsCounted() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isDeathsResetOnNewIsland() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAllowSetHomeInNether() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAllowSetHomeInTheEnd() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isRequireConfirmationToSetHomeInNether() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isRequireConfirmationToSetHomeInTheEnd() {
// TODO Auto-generated method stub
return false;
}
@Override
public int getBanLimit() {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean isLeaversLoseReset() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isKickedKeepInventory() {
// TODO Auto-generated method stub
return false;
}
}
}