getGeoLimitSettings();
+
+ /**
+ * @return reset limit for world
+ */
+ int getResetLimit();
+
+
+ /**
+ * Get the island reset time stamp. Any player who last logged in before this time will have resets zeroed
+ */
+ long getResetEpoch();
+
+ /**
+ * Set the island reset time stamp. Any player who last logged in before this time will have resets zeroed
+ */
+ void setResetEpoch(long timestamp);
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/BSBReadyEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/BSBReadyEvent.java
new file mode 100644
index 0000000..41da85e
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/BSBReadyEvent.java
@@ -0,0 +1,9 @@
+package us.tastybento.bskyblock.api.events;
+
+/**
+ * Fired when BSkyBlock is ready to play and all files are loaded
+ *
+ * @author tastybento
+ * @since 1.0
+ */
+public class BSBReadyEvent extends PremadeEvent {}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/IslandBaseEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/IslandBaseEvent.java
new file mode 100644
index 0000000..3d93c9d
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/IslandBaseEvent.java
@@ -0,0 +1,89 @@
+package us.tastybento.bskyblock.api.events;
+
+import java.util.UUID;
+
+import org.bukkit.Location;
+import org.bukkit.event.Cancellable;
+
+import us.tastybento.bskyblock.database.objects.Island;
+
+/**
+ *
+ * @author Poslovitch
+ * @version 1.0
+ */
+public class IslandBaseEvent extends PremadeEvent implements Cancellable {
+ private boolean cancelled;
+
+ private final Island island;
+ private final UUID playerUUID;
+ private final boolean admin;
+ private final Location location;
+
+ public IslandBaseEvent(Island island) {
+ super();
+ this.island = island;
+ playerUUID = island == null ? null : island.getOwner();
+ admin = false;
+ location = island == null ? null : island.getCenter();
+ }
+
+ /**
+ * @param island - island
+ * @param playerUUID - the player's UUID
+ * @param admin - true if ths is due to an admin event
+ * @param location - the location
+ */
+ public IslandBaseEvent(Island island, UUID playerUUID, boolean admin, Location location) {
+ super();
+ this.island = island;
+ this.playerUUID = playerUUID;
+ this.admin = admin;
+ this.location = location;
+ }
+
+ /**
+ * @return the island involved in this event
+ */
+ public Island getIsland(){
+ return island;
+ }
+
+ /**
+ * @return the owner of the island
+ */
+ public UUID getOwner() {
+ return island.getOwner();
+ }
+
+ /**
+ * @return the playerUUID
+ */
+ public UUID getPlayerUUID() {
+ return playerUUID;
+ }
+
+ /**
+ * @return the admin
+ */
+ public boolean isAdmin() {
+ return admin;
+ }
+
+ /**
+ * @return the location
+ */
+ public Location getLocation() {
+ return location;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ cancelled = cancel;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/PremadeEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/PremadeEvent.java
new file mode 100644
index 0000000..e1630e5
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/PremadeEvent.java
@@ -0,0 +1,18 @@
+package us.tastybento.bskyblock.api.events;
+
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+public abstract class PremadeEvent extends Event {
+
+ private static final HandlerList handlers = new HandlerList();
+
+ @Override
+ public HandlerList getHandlers() {
+ return getHandlerList();
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/addon/AddonBaseEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/addon/AddonBaseEvent.java
new file mode 100644
index 0000000..cc8c4d3
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/addon/AddonBaseEvent.java
@@ -0,0 +1,22 @@
+package us.tastybento.bskyblock.api.events.addon;
+
+import us.tastybento.bskyblock.api.addons.Addon;
+import us.tastybento.bskyblock.api.events.PremadeEvent;
+
+/**
+ * @author Poslovitch
+ * @since 1.0
+ */
+public class AddonBaseEvent extends PremadeEvent {
+
+ private final Addon addon;
+
+ public AddonBaseEvent(Addon addon) {
+ super();
+ this.addon = addon;
+ }
+
+ public Addon getAddon() {
+ return addon;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/addon/AddonEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/addon/AddonEvent.java
new file mode 100644
index 0000000..9a71f1b
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/addon/AddonEvent.java
@@ -0,0 +1,71 @@
+package us.tastybento.bskyblock.api.events.addon;
+
+import us.tastybento.bskyblock.api.addons.Addon;
+
+public class AddonEvent {
+
+ public enum Reason {
+ ENABLE,
+ DISABLE,
+ LOAD,
+ UNKNOWN
+ }
+
+ public static AddonEventBuilder builder() {
+ return new AddonEventBuilder();
+ }
+
+ public static class AddonEnableEvent extends AddonBaseEvent {
+ private AddonEnableEvent(Addon addon) {
+ // Final variables have to be declared in the constuctor
+ super(addon);
+ }
+ }
+ public static class AddonDisableEvent extends AddonBaseEvent {
+ private AddonDisableEvent(Addon addon) {
+ // Final variables have to be declared in the constuctor
+ super(addon);
+ }
+ }
+ public static class AddonLoadEvent extends AddonBaseEvent {
+ private AddonLoadEvent(Addon addon) {
+ // Final variables have to be declared in the constuctor
+ super(addon);
+ }
+ }
+ public static class AddonGeneralEvent extends AddonBaseEvent {
+ private AddonGeneralEvent(Addon addon) {
+ // Final variables have to be declared in the constuctor
+ super(addon);
+ }
+ }
+
+ public static class AddonEventBuilder {
+ // Here field are NOT final. They are just used for the building.
+ private Addon addon;
+ private Reason reason = Reason.UNKNOWN;
+
+ public AddonEventBuilder addon(Addon addon) {
+ this.addon = addon;
+ return this;
+ }
+
+ public AddonEventBuilder reason(Reason reason) {
+ this.reason = reason;
+ return this;
+ }
+
+ public AddonBaseEvent build() {
+ switch (reason) {
+ case ENABLE:
+ return new AddonEnableEvent(addon);
+ case DISABLE:
+ return new AddonDisableEvent(addon);
+ case LOAD:
+ return new AddonLoadEvent(addon);
+ default:
+ return new AddonGeneralEvent(addon);
+ }
+ }
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/command/CommandEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/command/CommandEvent.java
new file mode 100644
index 0000000..119ad86
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/command/CommandEvent.java
@@ -0,0 +1,94 @@
+package us.tastybento.bskyblock.api.events.command;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.event.Cancellable;
+
+import us.tastybento.bskyblock.api.events.PremadeEvent;
+
+/**
+ * Fired when a team event happens.
+ *
+ * @author tastybento
+ * @since 1.0
+ */
+public class CommandEvent extends PremadeEvent implements Cancellable {
+
+ private boolean cancelled;
+
+ private final CommandSender sender;
+ private final Command command;
+ private final String label;
+ private final String[] args;
+
+ private CommandEvent(CommandSender sender, Command command, String label, String[] args) {
+ super();
+ this.sender = sender;
+ this.command = command;
+ this.label = label;
+ this.args = args;
+ }
+
+ public static CommandEventBuilder builder() {
+ return new CommandEventBuilder();
+ }
+
+ public static class CommandEventBuilder {
+ // Here field are NOT final. They are just used for the building.
+ private CommandSender sender;
+ private Command command;
+ private String label;
+ private String[] args;
+
+ public CommandEventBuilder setSender(CommandSender sender) {
+ this.sender = sender;
+ return this;
+ }
+
+ public CommandEventBuilder setCommand(Command command) {
+ this.command = command;
+ return this;
+ }
+
+ public CommandEventBuilder setLabel(String label) {
+ this.label = label;
+ return this;
+ }
+
+ public CommandEventBuilder setArgs(String[] args) {
+ this.args = args;
+ return this;
+ }
+
+ public CommandEvent build() {
+ return new CommandEvent(sender, command, label, args);
+ }
+
+ }
+
+ public CommandSender getSender() {
+ return sender;
+ }
+
+ public Command getCommand() {
+ return command;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public String[] getArgs() {
+ return args;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean arg0) {
+ cancelled = arg0;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/island/FlagChangeEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/island/FlagChangeEvent.java
new file mode 100644
index 0000000..0d416b7
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/island/FlagChangeEvent.java
@@ -0,0 +1,55 @@
+package us.tastybento.bskyblock.api.events.island;
+
+import java.util.UUID;
+
+import us.tastybento.bskyblock.api.events.IslandBaseEvent;
+import us.tastybento.bskyblock.api.flags.Flag;
+import us.tastybento.bskyblock.database.objects.Island;
+
+/**
+ * This event is fired when a player changes a flag on his island
+ *
+ * Canceling this event will result in canceling the change.
+ *
+ * @author Poslovitch
+ * @since 1.0
+ */
+public class FlagChangeEvent extends IslandBaseEvent {
+ private final UUID player;
+ private final Flag editedFlag;
+ private final boolean setTo;
+
+ /**
+ * @param island - island
+ * @param player - the player
+ * @param editedFlag - flag edited
+ * @param setTo - new value
+ */
+ public FlagChangeEvent(Island island, UUID player, Flag editedFlag, boolean setTo) {
+ super(island);
+ this.player = player;
+ this.editedFlag = editedFlag;
+ this.setTo = setTo;
+ }
+
+ /**
+ * @return the player
+ */
+ public UUID getPlayer() {
+ return player;
+ }
+
+ /**
+ * @return the edited flag
+ */
+ public Flag getFlag() {
+ return editedFlag;
+ }
+
+ /**
+ * @return enabled/disabled
+ */
+ public boolean getSetTo() {
+ return setTo;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/island/IslandEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/island/IslandEvent.java
new file mode 100644
index 0000000..4804272
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/island/IslandEvent.java
@@ -0,0 +1,248 @@
+package us.tastybento.bskyblock.api.events.island;
+
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+
+import us.tastybento.bskyblock.api.events.IslandBaseEvent;
+import us.tastybento.bskyblock.database.objects.Island;
+
+/**
+ * Fired when a team event happens.
+ *
+ * @author tastybento
+ * @since 1.0
+ */
+public class IslandEvent {
+
+ /**
+ * Reason for the event
+ *
+ */
+ public enum Reason {
+ CREATE,
+ CREATED,
+ DELETE,
+ DELETED,
+ ENTER,
+ EXIT,
+ LOCK,
+ RESET,
+ RESETTED,
+ UNLOCK,
+ UNKNOWN
+ }
+
+ public static IslandEventBuilder builder() {
+ return new IslandEventBuilder();
+ }
+
+ /**
+ * Fired when an island is going to be created. May be canceled.
+ *
+ */
+ public static class IslandCreateEvent extends IslandBaseEvent {
+ private IslandCreateEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when an island is created.
+ *
+ */
+ public static class IslandCreatedEvent extends IslandBaseEvent {
+ private IslandCreatedEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when an island is going to be deleted. May be canceled.
+ *
+ */
+ public static class IslandDeleteEvent extends IslandBaseEvent {
+ private IslandDeleteEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when an island is deleted.
+ *
+ */
+ public static class IslandDeletedEvent extends IslandBaseEvent {
+ private IslandDeletedEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when an a player enters an island
+ *
+ */
+ public static class IslandEnterEvent extends IslandBaseEvent {
+ private IslandEnterEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when a player exits and island
+ *
+ */
+ public static class IslandExitEvent extends IslandBaseEvent {
+ private IslandExitEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when an island is locked
+ *
+ */
+ public static class IslandLockEvent extends IslandBaseEvent {
+ private IslandLockEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when an island is unlocked
+ *
+ */
+ public static class IslandUnlockEvent extends IslandBaseEvent {
+ private IslandUnlockEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when an island is going to be reset. May be canceled.
+ *
+ */
+ public static class IslandResetEvent extends IslandBaseEvent {
+ private IslandResetEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired after an island is reset
+ *
+ */
+ public static class IslandResettedEvent extends IslandBaseEvent {
+ private IslandResettedEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ /**
+ * Fired when something happens to the island not covered by other events
+ *
+ */
+ public static class IslandGeneralEvent extends IslandBaseEvent {
+ private IslandGeneralEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+
+ public static class IslandEventBuilder {
+ // Here field are NOT final. They are just used for the building.
+ private Island island;
+ private UUID player;
+ private Reason reason = Reason.UNKNOWN;
+ private boolean admin;
+ private Location location;
+
+ public IslandEventBuilder island(Island island) {
+ this.island = island;
+ return this;
+ }
+
+ /**
+ * True if this is an admin driven event
+ * @param admin - true if due to admin event
+ * @return TeamEvent
+ */
+ public IslandEventBuilder admin(boolean admin) {
+ this.admin = admin;
+ return this;
+ }
+
+ /**
+ * @param reason for the event
+ * @return IslandEventBuilder
+ */
+ public IslandEventBuilder reason(Reason reason) {
+ this.reason = reason;
+ return this;
+ }
+
+ /**
+ * @param player - the player involved in the event
+ * @return IslandEventBuilder
+ */
+ public IslandEventBuilder involvedPlayer(UUID player) {
+ this.player = player;
+ return this;
+ }
+
+ public IslandEventBuilder location(Location center) {
+ location = center;
+ return this;
+ }
+
+ public IslandBaseEvent build() {
+ switch (reason) {
+ case CREATE:
+ IslandCreateEvent create = new IslandCreateEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(create);
+ return create;
+ case CREATED:
+ IslandCreatedEvent created = new IslandCreatedEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(created);
+ return created;
+ case DELETE:
+ IslandDeleteEvent delete = new IslandDeleteEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(delete);
+ return delete;
+ case DELETED:
+ IslandDeletedEvent deleted = new IslandDeletedEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(deleted);
+ return deleted;
+ case ENTER:
+ IslandEnterEvent enter = new IslandEnterEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(enter);
+ return enter;
+ case EXIT:
+ IslandExitEvent exit = new IslandExitEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(exit);
+ return exit;
+ case LOCK:
+ IslandLockEvent lock = new IslandLockEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(lock);
+ return lock;
+ case RESET:
+ IslandResetEvent reset = new IslandResetEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(reset);
+ return reset;
+ case RESETTED:
+ IslandResettedEvent resetted = new IslandResettedEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(resetted);
+ return resetted;
+ case UNLOCK:
+ IslandUnlockEvent unlock = new IslandUnlockEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(unlock);
+ return unlock;
+ default:
+ IslandGeneralEvent general = new IslandGeneralEvent(island, player, admin, location);
+ Bukkit.getServer().getPluginManager().callEvent(general);
+ return general;
+ }
+
+ }
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/purge/PurgeDeleteIslandEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/purge/PurgeDeleteIslandEvent.java
new file mode 100644
index 0000000..9f43602
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/purge/PurgeDeleteIslandEvent.java
@@ -0,0 +1,22 @@
+package us.tastybento.bskyblock.api.events.purge;
+
+import us.tastybento.bskyblock.api.events.IslandBaseEvent;
+import us.tastybento.bskyblock.database.objects.Island;
+
+/**
+ * This event is fired before an island is going to be purged.
+ * Canceling this event will prevent the plugin to remove the island.
+ *
+ * @author Poslovitch
+ * @since 1.0
+ */
+public class PurgeDeleteIslandEvent extends IslandBaseEvent {
+
+ /**
+ * Called to create the event
+ * @param island - island that will be removed
+ */
+ public PurgeDeleteIslandEvent(Island island) {
+ super(island);
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/purge/PurgeStartEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/purge/PurgeStartEvent.java
new file mode 100644
index 0000000..2ecae33
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/purge/PurgeStartEvent.java
@@ -0,0 +1,85 @@
+package us.tastybento.bskyblock.api.events.purge;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.bukkit.event.Cancellable;
+
+import us.tastybento.bskyblock.api.events.PremadeEvent;
+
+/**
+ * This event is fired when islands to remove have been chosen and before starting to remove them.
+ * You can remove or add islands to remove.
+ * Canceling this event will cancel the purge
+ *
+ * @author Poslovitch
+ * @since 1.0
+ */
+public class PurgeStartEvent extends PremadeEvent implements Cancellable {
+ private boolean cancelled;
+
+ private final UUID user;
+ private List islandsList;
+
+ /**
+ * Called to create the event
+ * @param user - the User - the UUID of the player who launched the purge, may be null if purge is launched using the console.
+ * @param islandsList - the list of islands to remove, based on their leader's UUID
+ */
+ public PurgeStartEvent(UUID user, List islandsList) {
+ this.user = user;
+ this.islandsList = islandsList;
+ }
+
+ /**
+ * @return the user who launched the purge, may be null if purge is launched using the console.
+ */
+ public UUID getUser( ){
+ return user;
+ }
+
+ /**
+ * @return the list of islands to remove, based on their leader's UUID
+ */
+ public List getIslandsList() {
+ return islandsList;
+ }
+
+ /**
+ * Convenience method to directly add an island owner's UUID to the list
+ * @param islandOwner - the owner's UUID from the island to remove
+ */
+ public void add(UUID islandOwner) {
+ if(!islandsList.contains(islandOwner)) {
+ islandsList.add(islandOwner);
+ }
+ }
+
+ /**
+ * Convenience method to directly remove an island owner's UUID to the list
+ * @param islandOwner - the owner's UUID from the island to remove
+ */
+ public void remove(UUID islandOwner) {
+ if(islandsList.contains(islandOwner)) {
+ islandsList.remove(islandOwner);
+ }
+ }
+
+ /**
+ * Replace the island list
+ * @param islandsList - a new island owners' UUIDs list
+ */
+ public void setIslandsList(List islandsList) {
+ this.islandsList = islandsList;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ cancelled = cancel;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/events/team/TeamEvent.java b/src/main/java/us/tastybento/bskyblock/api/events/team/TeamEvent.java
new file mode 100644
index 0000000..c53daba
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/events/team/TeamEvent.java
@@ -0,0 +1,166 @@
+package us.tastybento.bskyblock.api.events.team;
+
+import java.util.UUID;
+
+import org.bukkit.Location;
+
+import us.tastybento.bskyblock.api.events.IslandBaseEvent;
+import us.tastybento.bskyblock.database.objects.Island;
+
+/**
+ * Fired when a team event happens.
+ *
+ * @author tastybento
+ * @since 1.0
+ */
+public class TeamEvent {
+
+ public enum Reason {
+ INVITE,
+ JOIN,
+ REJECT,
+ LEAVE,
+ KICK,
+ MAKELEADER,
+ INFO,
+ DELETE,
+ UNKNOWN,
+ UNINVITE
+ }
+
+ public static TeamEventBuilder builder() {
+ return new TeamEventBuilder();
+ }
+
+ public static class TeamJoinEvent extends IslandBaseEvent {
+ private TeamJoinEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamInviteEvent extends IslandBaseEvent {
+ private TeamInviteEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamLeaveEvent extends IslandBaseEvent {
+ private TeamLeaveEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamRejectEvent extends IslandBaseEvent {
+ private TeamRejectEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamKickEvent extends IslandBaseEvent {
+ private TeamKickEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamMakeLeaderEvent extends IslandBaseEvent {
+ private TeamMakeLeaderEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamInfoEvent extends IslandBaseEvent {
+ private TeamInfoEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamDeleteEvent extends IslandBaseEvent {
+ private TeamDeleteEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamUninviteEvent extends IslandBaseEvent {
+ private TeamUninviteEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+ public static class TeamGeneralEvent extends IslandBaseEvent {
+ private TeamGeneralEvent(Island island, UUID player, boolean admin, Location location) {
+ // Final variables have to be declared in the constuctor
+ super(island, player, admin, location);
+ }
+ }
+
+ public static class TeamEventBuilder {
+ private Island island;
+ private UUID player;
+ private Reason reason = Reason.UNKNOWN;
+ private boolean admin;
+ private Location location;
+
+ public TeamEventBuilder island(Island island) {
+ this.island = island;
+ return this;
+ }
+
+ /**
+ * True if this is an admin driven event
+ * @param admin - true if due to an admin event
+ * @return TeamEvent
+ */
+ public TeamEventBuilder admin(boolean admin) {
+ this.admin = admin;
+ return this;
+ }
+
+ /**
+ * @param reason for the event
+ * @return TeamEventBuilder
+ */
+ public TeamEventBuilder reason(Reason reason) {
+ this.reason = reason;
+ return this;
+ }
+
+ /**
+ * @param player - the player involved in the event
+ * @return TeamEventBuilder
+ */
+ public TeamEventBuilder involvedPlayer(UUID player) {
+ this.player = player;
+ return this;
+ }
+
+ public TeamEventBuilder location(Location center) {
+ location = center;
+ return this;
+ }
+
+ public IslandBaseEvent build() {
+ switch (reason) {
+ case JOIN:
+ return new TeamJoinEvent(island, player, admin, location);
+ case INVITE:
+ return new TeamInviteEvent(island, player, admin, location);
+ case LEAVE:
+ return new TeamLeaveEvent(island, player, admin, location);
+ case REJECT:
+ return new TeamRejectEvent(island, player, admin, location);
+ case KICK:
+ return new TeamKickEvent(island, player, admin, location);
+ case MAKELEADER:
+ return new TeamMakeLeaderEvent(island, player, admin, location);
+ case INFO:
+ return new TeamInfoEvent(island, player, admin, location);
+ case DELETE:
+ return new TeamDeleteEvent(island, player, admin, location);
+ case UNINVITE:
+ return new TeamUninviteEvent(island, player, admin, location);
+ default:
+ return new TeamGeneralEvent(island, player, admin, location);
+ }
+ }
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/flags/AbstractFlagListener.java b/src/main/java/us/tastybento/bskyblock/api/flags/AbstractFlagListener.java
new file mode 100644
index 0000000..9941baa
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/flags/AbstractFlagListener.java
@@ -0,0 +1,202 @@
+package us.tastybento.bskyblock.api.flags;
+
+import java.lang.reflect.Method;
+import java.util.Optional;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.Listener;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.localization.TextVariables;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.database.objects.Island;
+import us.tastybento.bskyblock.managers.IslandWorldManager;
+import us.tastybento.bskyblock.managers.IslandsManager;
+
+/**
+ * Abstract class for flag listeners. Provides common code.
+ * @author tastybento
+ *
+ */
+public abstract class AbstractFlagListener implements Listener {
+
+ private BSkyBlock plugin = BSkyBlock.getInstance();
+ private User user = null;
+
+ /**
+ * @return the plugin
+ */
+ public BSkyBlock getPlugin() {
+ return plugin;
+ }
+
+ /**
+ * Used for unit testing only to set the plugin
+ * @param plugin - BSkyBlock plugin object
+ */
+ public void setPlugin(BSkyBlock plugin) {
+ this.plugin = plugin;
+ }
+
+ /**
+ * Sets the player associated with this event.
+ * If the user is a fake player, they are not counted.
+ * @param e - event
+ * @return true if found, otherwise false
+ */
+ private boolean createEventUser(Event e) {
+ try {
+ // Use reflection to get the getPlayer method if it exists
+ Method getPlayer = e.getClass().getMethod("getPlayer");
+ if (getPlayer != null) {
+ setUser(User.getInstance((Player)getPlayer.invoke(e)));
+ return true;
+ }
+ } catch (Exception e1) { // Do nothing
+ }
+ return false;
+ }
+
+ /**
+ * Explicitly set the user for the next {@link #checkIsland(Event, Location, Flag)} or {@link #checkIsland(Event, Location, Flag, boolean)}
+ * @param user - the User
+ */
+ public AbstractFlagListener setUser(User user) {
+ if (!plugin.getSettings().getFakePlayers().contains(user.getName())) {
+ this.user = user;
+ }
+ return this;
+ }
+
+ /*
+ * The following methods cover the cancellable events and enable a simple noGo(e) to be used to cancel and send the error message
+ */
+
+ /**
+ * Cancels the event and sends the island public message to user
+ * @param e - event
+ * @param flag - the flag that has been checked
+ */
+ public void noGo(Event e, Flag flag) {
+ noGo(e, flag, false);
+ }
+
+ /**
+ * Cancels the event and sends the island protected message to user unless silent is true
+ * @param e - event
+ * @param flag - the flag that has been checked
+ * @param silent - if true, message is not sent
+ */
+ public void noGo(Event e, Flag flag, boolean silent) {
+ if (e instanceof Cancellable) {
+ ((Cancellable)e).setCancelled(true);
+ }
+ if (user != null) {
+ if (!silent) {
+ user.notify("protection.protected", TextVariables.DESCRIPTION, user.getTranslation(flag.getHintReference()));
+ }
+ user.updateInventory();
+ }
+ }
+
+ /**
+ * Check if flag is allowed at location
+ * @param e - event
+ * @param loc - location
+ * @param flag - flag {@link us.tastybento.bskyblock.lists.Flags}
+ * @return true if allowed, false if not
+ */
+ public boolean checkIsland(Event e, Location loc, Flag flag) {
+ return checkIsland(e, loc, flag, false);
+ }
+
+ /**
+ * Check if flag is allowed at location
+ * @param e - event
+ * @param loc - location
+ * @param flag - flag {@link us.tastybento.bskyblock.lists.Flags}
+ * @param silent - if true, no attempt is made to tell the user
+ * @return true if the check is okay, false if it was disallowed
+ */
+ public boolean checkIsland(Event e, Location loc, Flag flag, boolean silent) {
+ // If this is not an Island World, skip
+ if (!plugin.getIWM().inWorld(loc)) {
+ return true;
+ }
+ // Get the island and if present
+ Optional island = getIslands().getProtectedIslandAt(loc);
+ // Handle Settings Flag
+ if (flag.getType().equals(Flag.Type.SETTING)) {
+ // If the island exists, return the setting, otherwise return the default setting for this flag
+ return island.map(x -> x.isAllowed(flag)).orElse(flag.isSetForWorld(loc.getWorld()));
+ }
+
+ // Protection flag
+ // If the user is not set already, try to get it from the event
+ // Set the user associated with this event
+ // The user is not set, and the event does not hold a getPlayer, so return false
+ // TODO: is this the correct handling here?
+ if (user == null && !createEventUser(e)) {
+ plugin.logError("Check island had no associated user! " + e.getEventName());
+ return false;
+ }
+
+ // Ops or bypass mods can do anything
+ if (user.isOp() || user.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + ".mod.bypassprotect")) {
+ user = null;
+ return true;
+ }
+
+ // Check if the plugin is set in User (required for testing)
+ User.setPlugin(plugin);
+
+ if (island.isPresent()) {
+ if (!island.get().isAllowed(user, flag)) {
+ noGo(e, flag, silent);
+ // Clear the user for the next time
+ user = null;
+ return false;
+ } else {
+ user = null;
+ return true;
+ }
+ }
+ // The player is in the world, but not on an island, so general world settings apply
+ if (!flag.isSetForWorld(loc.getWorld())) {
+ noGo(e, flag, silent);
+ user = null;
+ return false;
+ } else {
+ user = null;
+ return true;
+ }
+ }
+
+ /**
+ * Get the flag for this ID
+ * @param id - the flag ID
+ * @return Flag denoted by the id
+ */
+ protected Flag id(String id) {
+ return plugin.getFlagsManager().getFlagByID(id);
+ }
+
+ /**
+ * Get the island database manager
+ * @return the island database manager
+ */
+ protected IslandsManager getIslands() {
+ return plugin.getIslands();
+ }
+
+ /**
+ * Get the island world manager
+ * @return Island World Manager
+ */
+ protected IslandWorldManager getIWM() {
+ return plugin.getIWM();
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/flags/Flag.java b/src/main/java/us/tastybento/bskyblock/api/flags/Flag.java
new file mode 100644
index 0000000..2aba316
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/flags/Flag.java
@@ -0,0 +1,250 @@
+package us.tastybento.bskyblock.api.flags;
+
+import java.util.Optional;
+
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.event.Listener;
+import org.bukkit.inventory.ItemStack;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.configuration.WorldSettings;
+import us.tastybento.bskyblock.api.localization.TextVariables;
+import us.tastybento.bskyblock.api.panels.PanelItem;
+import us.tastybento.bskyblock.api.panels.builders.PanelItemBuilder;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.database.objects.Island;
+import us.tastybento.bskyblock.managers.RanksManager;
+
+public class Flag implements Comparable {
+
+ public enum Type {
+ PROTECTION(Material.SHIELD),
+ SETTING(Material.COMMAND),
+ WORLD_SETTING(Material.GRASS);
+
+ private Material icon;
+
+ Type(Material icon) {
+ this.icon = icon;
+ }
+
+ public Material getIcon() {
+ return icon;
+ }
+ }
+
+ private static final String PROTECTION_FLAGS = "protection.flags.";
+
+ private final String id;
+ private final Material icon;
+ private final Listener listener;
+ private final Type type;
+ private boolean setting;
+ private final int defaultRank;
+ private final PanelItem.ClickHandler clickHandler;
+ private final boolean subPanel;
+
+ Flag(String id, Material icon, Listener listener, Type type, int defaultRank, PanelItem.ClickHandler clickListener, boolean subPanel) {
+ this.id = id;
+ this.icon = icon;
+ this.listener = listener;
+ this.type = type;
+ this.defaultRank = defaultRank;
+ this.clickHandler = clickListener;
+ this.subPanel = subPanel;
+ }
+
+ public String getID() {
+ return id;
+ }
+
+ public Material getIcon() {
+ return icon;
+ }
+
+ public Optional getListener() {
+ return Optional.ofNullable(listener);
+ }
+
+ /**
+ * Check if a setting is set in this world
+ * @param world - world
+ * @return world setting or default flag setting if a specific world setting is not set.
+ * If world is not a game world, then the result will always be false!
+ */
+ public boolean isSetForWorld(World world) {
+ if (type.equals(Type.WORLD_SETTING)) {
+ WorldSettings ws = BSkyBlock.getInstance().getIWM().getWorldSettings(world);
+ if (ws != null) {
+ ws.getWorldFlags().putIfAbsent(getID(), setting);
+ return ws.getWorldFlags().get(getID());
+ }
+ return false;
+ } else {
+ // Setting
+ return setting;
+ }
+ }
+
+ /**
+ * Set a world setting
+ * @param world - world
+ * @param setting - true or false
+ */
+ public void setSetting(World world, boolean setting) {
+ if (getType().equals(Type.WORLD_SETTING)) {
+ BSkyBlock.getInstance().getIWM().getWorldSettings(world).getWorldFlags().put(getID(), setting);
+ }
+ }
+
+ /**
+ * Set the status of this flag for locations outside of island spaces
+ * @param defaultSetting - true means it is allowed. false means it is not allowed
+ */
+ public void setDefaultSetting(boolean defaultSetting) {
+ this.setting = defaultSetting;
+ }
+
+ /**
+ * @return the type
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * @return the defaultRank
+ */
+ public int getDefaultRank() {
+ return defaultRank;
+ }
+
+ /**
+ * @return whether the flag uses a subpanel or not
+ */
+ public boolean hasSubPanel() {
+ return subPanel;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ /* (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 Flag)) {
+ return false;
+ }
+ Flag other = (Flag) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+
+ return type == other.type;
+ }
+
+ public String getNameReference() {
+ return PROTECTION_FLAGS + this.id + ".name";
+ }
+
+ public String getDescriptionReference() {
+ return PROTECTION_FLAGS + this.id + ".description";
+ }
+
+ public String getHintReference() {
+ return PROTECTION_FLAGS + this.id + ".hint";
+ }
+
+ /**
+ * Converts a flag to a panel item. The content of the flag will change depending on who the user is and where they are.
+ * @param plugin - plugin
+ * @param user - user that will see this flag
+ * @return - PanelItem for this flag
+ */
+ public PanelItem toPanelItem(BSkyBlock plugin, User user) {
+ // Start the flag conversion
+ PanelItemBuilder pib = new PanelItemBuilder()
+ .icon(new ItemStack(icon))
+ .name(user.getTranslation("protection.panel.flag-item.name-layout", TextVariables.NAME, user.getTranslation(getNameReference())))
+ .clickHandler(clickHandler);
+ if (hasSubPanel()) {
+ pib.description(user.getTranslation("protection.panel.flag-item.menu-layout", TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
+ return pib.build();
+ }
+ // Check if this is a setting or world setting
+ if (getType().equals(Type.WORLD_SETTING)) {
+ String worldDetting = this.isSetForWorld(user.getWorld()) ? user.getTranslation("protection.panel.flag-item.setting-active")
+ : user.getTranslation("protection.panel.flag-item.setting-disabled");
+ pib.description(user.getTranslation("protection.panel.flag-item.setting-layout", TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())
+ , "[setting]", worldDetting));
+ return pib.build();
+ }
+
+ // Get the island this user is on or their own
+ Island island = plugin.getIslands().getIslandAt(user.getLocation()).orElse(plugin.getIslands().getIsland(user.getWorld(), user.getUniqueId()));
+ if (island != null) {
+ if (getType().equals(Type.SETTING)) {
+ String islandSetting = island.isAllowed(this) ? user.getTranslation("protection.panel.flag-item.setting-active")
+ : user.getTranslation("protection.panel.flag-item.setting-disabled");
+ pib.description(user.getTranslation("protection.panel.flag-item.setting-layout", TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())
+ , "[setting]", islandSetting));
+ return pib.build();
+ }
+ // TODO: Get the world settings - the player has no island and is not in an island location
+ // Dynamic rank list
+ if (getType().equals(Type.PROTECTION)) {
+ // Protection flag
+ String d = user.getTranslation(getDescriptionReference());
+ d = user.getTranslation("protection.panel.flag-item.description-layout", TextVariables.DESCRIPTION, d);
+ pib.description(d);
+ plugin.getRanksManager().getRanks().forEach((reference, score) -> {
+ if (score > RanksManager.BANNED_RANK && score < island.getFlag(this)) {
+ pib.description(user.getTranslation("protection.panel.flag-item.blocked_rank") + user.getTranslation(reference));
+ } else if (score <= RanksManager.OWNER_RANK && score > island.getFlag(this)) {
+ pib.description(user.getTranslation("protection.panel.flag-item.allowed_rank") + user.getTranslation(reference));
+ } else if (score == island.getFlag(this)) {
+ pib.description(user.getTranslation("protection.panel.flag-item.minimal_rank") + user.getTranslation(reference));
+ }
+ });
+ }
+ }
+ return pib.build();
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Flag [id=" + id + ", icon=" + icon + ", listener=" + listener + ", type=" + type + ", defaultSetting="
+ + setting + ", defaultRank=" + defaultRank + ", clickHandler=" + clickHandler + ", subPanel=" + subPanel + "]";
+ }
+
+ @Override
+ public int compareTo(Flag o) {
+ return getID().compareTo(o.getID());
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/flags/FlagBuilder.java b/src/main/java/us/tastybento/bskyblock/api/flags/FlagBuilder.java
new file mode 100644
index 0000000..d1bc5e9
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/flags/FlagBuilder.java
@@ -0,0 +1,131 @@
+package us.tastybento.bskyblock.api.flags;
+
+import org.bukkit.Material;
+import org.bukkit.event.Listener;
+
+import us.tastybento.bskyblock.api.flags.Flag.Type;
+import us.tastybento.bskyblock.api.flags.clicklisteners.CycleClick;
+import us.tastybento.bskyblock.api.flags.clicklisteners.IslandToggleClick;
+import us.tastybento.bskyblock.api.flags.clicklisteners.WorldToggleClick;
+import us.tastybento.bskyblock.api.panels.PanelItem;
+import us.tastybento.bskyblock.managers.RanksManager;
+
+public class FlagBuilder {
+
+ private String id;
+ private Material icon;
+ private Listener listener;
+ private boolean setting;
+ private Type type = Type.PROTECTION;
+ private int defaultRank = RanksManager.MEMBER_RANK;
+ private PanelItem.ClickHandler onClick;
+ private boolean subPanel = false;
+
+ public FlagBuilder id(String string) {
+ id = string;
+ return this;
+ }
+
+ /**
+ * The material that will become the icon for this flag
+ * @param icon - material
+ */
+ public FlagBuilder icon(Material icon) {
+ this.icon = icon;
+ return this;
+ }
+
+ /**
+ * @param listener - the Bukkit listener that will be registered to handle this flag
+ */
+ public FlagBuilder listener(Listener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ public Flag build() {
+ // If no onClick has been set, then apply default ones
+ if (onClick == null) {
+ switch (type){
+ case PROTECTION:
+ onClick = new CycleClick(id);
+ break;
+ case SETTING:
+ onClick = new IslandToggleClick(id);
+ break;
+ case WORLD_SETTING:
+ onClick = new WorldToggleClick(id);
+ break;
+ default:
+ onClick = new CycleClick(id);
+ break;
+ }
+ }
+
+ Flag f = new Flag(id, icon, listener, type, defaultRank, onClick, subPanel);
+ f.setDefaultSetting(setting);
+ return f;
+ }
+
+ /**
+ * Sets the default setting for this flag in the world
+ * @param setting - true or false
+ * @return FlagBuilder
+ */
+ public FlagBuilder allowedByDefault(boolean setting) {
+ this.setting = setting;
+ return this;
+ }
+
+ /**
+ * Set the type of this flag
+ * @param type {@link Type}
+ * @return FlagBuilder
+ */
+ public FlagBuilder type(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * Set the id of this flag to the name of this enum value
+ * @param flag - flag
+ * @return FlagBuilder
+ */
+ public FlagBuilder id(Enum> flag) {
+ id = flag.name();
+ return this;
+ }
+
+ /**
+ * Set a default rank for this flag. If not set, the value of RanksManager.MEMBER_RANK will be used
+ * @param rank - rank value
+ * @return FlagBuilder
+ */
+ public FlagBuilder defaultRank(int rank) {
+ this.defaultRank = rank;
+ return this;
+ }
+
+ /**
+ * Adds a listener for clicks on this flag when it is a panel item. Default is
+ * {@link us.tastybento.bskyblock.api.flags.clicklisteners.CycleClick}
+ * @param onClickListener - the listener for clicks. Must use the ClickOn interface
+ * @return FlagBuilder
+ */
+ public FlagBuilder onClick(PanelItem.ClickHandler onClickListener) {
+ this.onClick = onClickListener;
+ return this;
+ }
+
+ /**
+ * Marks this flag as "using a sub-panel"
+ * @param subPanel - whether the flag will use a sub-panel or not
+ * @return FlagBuilder
+ */
+ public FlagBuilder subPanel(boolean subPanel) {
+ this.subPanel = subPanel;
+ return this;
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/CycleClick.java b/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/CycleClick.java
new file mode 100644
index 0000000..f2a2607
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/CycleClick.java
@@ -0,0 +1,77 @@
+package us.tastybento.bskyblock.api.flags.clicklisteners;
+
+import org.bukkit.Sound;
+import org.bukkit.event.inventory.ClickType;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.flags.Flag;
+import us.tastybento.bskyblock.api.panels.Panel;
+import us.tastybento.bskyblock.api.panels.PanelItem;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.database.objects.Island;
+import us.tastybento.bskyblock.managers.RanksManager;
+import us.tastybento.bskyblock.util.Util;
+
+/**
+ * Left Clicks increase rank, right clicks lower rank
+ * @author tastybento
+ *
+ */
+public class CycleClick implements PanelItem.ClickHandler {
+
+ private BSkyBlock plugin = BSkyBlock.getInstance();
+ private final String id;
+
+ /**
+ * @param id - the flag id that will be adjusted by this click
+ */
+ public CycleClick(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public boolean onClick(Panel panel, User user, ClickType click, int slot) {
+ // Get the world
+ if (!plugin.getIWM().inWorld(user.getLocation())) {
+ user.sendMessage("general.errors.wrong-world");
+ return true;
+ }
+ String reqPerm = plugin.getIWM().getPermissionPrefix(Util.getWorld(user.getWorld())) + ".settings." + id;
+ if (!user.hasPermission(reqPerm)) {
+ user.sendMessage("general.errors.no-permission");
+ user.sendMessage("general.errors.you-need", "[permission]", reqPerm);
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
+ return true;
+ }
+ // Left clicking increases the rank required
+ // Right clicking decreases the rank required
+ // Get the user's island
+ Island island = plugin.getIslands().getIsland(user.getWorld(), user.getUniqueId());
+ if (island != null && island.getOwner().equals(user.getUniqueId())) {
+ RanksManager rm = plugin.getRanksManager();
+ Flag flag = plugin.getFlagsManager().getFlagByID(id);
+ int currentRank = island.getFlag(flag);
+ if (click.equals(ClickType.LEFT)) {
+ if (currentRank == RanksManager.OWNER_RANK) {
+ island.setFlag(flag, RanksManager.VISITOR_RANK);
+ } else {
+ island.setFlag(flag, rm.getRankUpValue(currentRank));
+ }
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+ } else if (click.equals(ClickType.RIGHT)) {
+ if (currentRank == RanksManager.VISITOR_RANK) {
+ island.setFlag(flag, RanksManager.OWNER_RANK);
+ } else {
+ island.setFlag(flag, rm.getRankDownValue(currentRank));
+ }
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+ }
+ // Apply change to panel
+ panel.getInventory().setItem(slot, flag.toPanelItem(plugin, user).getItem());
+ } else {
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/IslandToggleClick.java b/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/IslandToggleClick.java
new file mode 100644
index 0000000..d348331
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/IslandToggleClick.java
@@ -0,0 +1,66 @@
+/*
+
+ */
+package us.tastybento.bskyblock.api.flags.clicklisteners;
+
+import org.bukkit.Sound;
+import org.bukkit.event.inventory.ClickType;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.flags.Flag;
+import us.tastybento.bskyblock.api.panels.Panel;
+import us.tastybento.bskyblock.api.panels.PanelItem.ClickHandler;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.database.objects.Island;
+import us.tastybento.bskyblock.util.Util;
+
+/**
+ * Toggles a island setting on/off
+ * @author tastybento
+ *
+ */
+public class IslandToggleClick implements ClickHandler {
+
+ private BSkyBlock plugin = BSkyBlock.getInstance();
+ private String id;
+
+ /**
+ * @param id - the flag ID that this click listener is associated with
+ */
+ public IslandToggleClick(String id) {
+ this.id = id;
+ }
+
+ /* (non-Javadoc)
+ * @see us.tastybento.bskyblock.api.panels.PanelItem.ClickHandler#onClick(us.tastybento.bskyblock.api.panels.Panel, us.tastybento.bskyblock.api.user.User, org.bukkit.event.inventory.ClickType, int)
+ */
+ @Override
+ public boolean onClick(Panel panel, User user, ClickType clickType, int slot) {
+ // Get the world
+ if (!plugin.getIWM().inWorld(user.getLocation())) {
+ user.sendMessage("general.errors.wrong-world");
+ return true;
+ }
+ String reqPerm = plugin.getIWM().getPermissionPrefix(Util.getWorld(user.getWorld())) + ".settings." + id;
+ if (!user.hasPermission(reqPerm)) {
+ user.sendMessage("general.errors.no-permission");
+ user.sendMessage("general.errors.you-need", "[permission]", reqPerm);
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
+ return true;
+ }
+ // Get the user's island
+ Island island = plugin.getIslands().getIsland(user.getWorld(), user);
+ if (island != null && island.getOwner().equals(user.getUniqueId())) {
+ Flag flag = plugin.getFlagsManager().getFlagByID(id);
+ // Toggle flag
+ island.toggleFlag(flag);
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+ // Apply change to panel
+ panel.getInventory().setItem(slot, flag.toPanelItem(plugin, user).getItem());
+ } else {
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/WorldToggleClick.java b/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/WorldToggleClick.java
new file mode 100644
index 0000000..e0b4742
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/flags/clicklisteners/WorldToggleClick.java
@@ -0,0 +1,61 @@
+/*
+
+ */
+package us.tastybento.bskyblock.api.flags.clicklisteners;
+
+import org.bukkit.Sound;
+import org.bukkit.event.inventory.ClickType;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.flags.Flag;
+import us.tastybento.bskyblock.api.panels.Panel;
+import us.tastybento.bskyblock.api.panels.PanelItem.ClickHandler;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.util.Util;
+
+/**
+ * Toggles a worldwide setting on/off
+ * @author tastybento
+ *
+ */
+public class WorldToggleClick implements ClickHandler {
+
+ private BSkyBlock plugin = BSkyBlock.getInstance();
+ private String id;
+
+ /**
+ * @param id - the flag ID that this click listener is associated with
+ */
+ public WorldToggleClick(String id) {
+ this.id = id;
+ }
+
+
+ /* (non-Javadoc)
+ * @see us.tastybento.bskyblock.api.panels.PanelItem.ClickHandler#onClick(us.tastybento.bskyblock.api.panels.Panel, us.tastybento.bskyblock.api.user.User, org.bukkit.event.inventory.ClickType, int)
+ */
+ @Override
+ public boolean onClick(Panel panel, User user, ClickType clickType, int slot) {
+ // Get the world
+ if (!plugin.getIWM().inWorld(user.getLocation())) {
+ user.sendMessage("general.errors.wrong-world");
+ return true;
+ }
+ String reqPerm = plugin.getIWM().getPermissionPrefix(Util.getWorld(user.getWorld())) + ".admin.world.settings." + id;
+ if (!user.hasPermission(reqPerm)) {
+ user.sendMessage("general.errors.no-permission");
+ user.sendMessage("general.errors.you-need", "[permission]", reqPerm);
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
+ return true;
+ }
+ // Get flag
+ Flag flag = plugin.getFlagsManager().getFlagByID(id);
+ // Toggle flag
+ flag.setSetting(user.getWorld(), !flag.isSetForWorld(user.getWorld()));
+ user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+ // Apply change to panel
+ panel.getInventory().setItem(slot, flag.toPanelItem(plugin, user).getItem());
+ return true;
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/localization/BSBLocale.java b/src/main/java/us/tastybento/bskyblock/api/localization/BSBLocale.java
new file mode 100644
index 0000000..0dc06fb
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/localization/BSBLocale.java
@@ -0,0 +1,96 @@
+package us.tastybento.bskyblock.api.localization;
+
+import java.io.File;
+import java.util.Locale;
+
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.inventory.ItemStack;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.util.ItemParser;
+
+/**
+ * @author Poslovitch, tastybento
+ */
+public class BSBLocale {
+
+ private Locale locale;
+ private YamlConfiguration config;
+ private ItemStack banner;
+
+ public BSBLocale(Locale locale, YamlConfiguration config) {
+ this.locale = locale;
+ this.config = config;
+
+ // Load the banner from the configuration
+ banner = ItemParser.parse(config.getString("banner"));
+ }
+
+ /**
+ * Get text from the yml file for this locale
+ * @param reference - the YAML node where the text is
+ * @return Text for this locale reference or the reference if nothing has been found
+ */
+ public String get(String reference) {
+ if (config.contains(reference)) {
+ return config.getString(reference);
+ }
+ return reference; // return reference in case nothing has been found
+ }
+
+ /**
+ * Returns the locale language
+ * @return the locale language
+ */
+ public String getLanguage(){
+ if(locale == null) {
+ return "unknown";
+ }
+
+ return locale.getDisplayLanguage();
+ }
+
+ /**
+ * Returns the locale country
+ * @return the locale country
+ */
+ public String getCountry(){
+ if(locale == null) {
+ return "unknown";
+ }
+
+ return locale.getDisplayCountry();
+ }
+
+ /**
+ * Returns the locale language tag (e.g: en-GB)
+ * @return the locale language tag
+ */
+ public String toLanguageTag(){
+ return locale.toLanguageTag();
+ }
+
+ /**
+ * Returns the banner ItemStack representing this locale
+ * @return the banner ItemStack
+ */
+ public ItemStack getBanner() {
+ return banner;
+ }
+
+ /**
+ * Merges a language YAML file to this locale
+ * @param toBeMerged the YamlConfiguration of the language file
+ */
+ public void merge(YamlConfiguration toBeMerged) {
+ for (String key : toBeMerged.getKeys(true)) {
+ if (!config.contains(key)) {
+ config.set(key, toBeMerged.get(key));
+ }
+ }
+ }
+
+ public boolean contains(String reference) {
+ return config.contains(reference);
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/localization/TextVariables.java b/src/main/java/us/tastybento/bskyblock/api/localization/TextVariables.java
new file mode 100644
index 0000000..3f0bcf3
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/localization/TextVariables.java
@@ -0,0 +1,19 @@
+package us.tastybento.bskyblock.api.localization;
+
+/**
+ * Contains the common variables that can be used in texts.
+ * @author Poslovitch
+ */
+public class TextVariables {
+
+ private TextVariables() {}
+
+ public static final String NAME = "[name]";
+ public static final String DESCRIPTION = "[description]";
+ public static final String NUMBER = "[number]";
+ public static final String RANK = "[rank]";
+ public static final String LABEL = "[label]";
+ public static final String PERMISSION = "[permission]";
+ public static final String SPAWN_HERE = "[spawn_here]";
+ public static final String VERSION = "[version]";
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/panels/Panel.java b/src/main/java/us/tastybento/bskyblock/api/panels/Panel.java
new file mode 100644
index 0000000..fcb2add
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/panels/Panel.java
@@ -0,0 +1,147 @@
+package us.tastybento.bskyblock.api.panels;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.listeners.PanelListenerManager;
+import us.tastybento.bskyblock.util.HeadGetter;
+import us.tastybento.bskyblock.util.HeadRequester;
+
+public class Panel implements HeadRequester {
+
+ private Inventory inventory;
+ private Map items;
+ private PanelListener listener;
+ private User user;
+ private final String name;
+
+ public Panel(String name, Map items, int size, User user, PanelListener listener) {
+ this.name = name;
+ this.items = items;
+ // If size is undefined (0) then use the number of items
+ if (size == 0) {
+ size = items.keySet().size();
+ }
+ // Create panel
+ if (size > 0) {
+ // Make sure size is a multiple of 9
+ size = size + 8;
+ size -= (size % 9);
+ inventory = Bukkit.createInventory(null, size, name);
+ // Fill the inventory and return
+ for (Map.Entry en: items.entrySet()) {
+ //TODO allow multi-paging
+ if (en.getKey() < 54) inventory.setItem(en.getKey(), en.getValue().getItem());
+ // Get player head async
+ if (en.getValue().isPlayerHead()) {
+ HeadGetter.getHead(en.getValue(), this);
+ }
+ }
+ } else {
+ inventory = Bukkit.createInventory(null, 9, name);
+ }
+ this.listener = listener;
+ // If the listener is defined, then run setup
+ if (listener != null) listener.setup();
+
+ // If the user is defined, then open panel immediately
+ this.user = user;
+ if (user != null) this.open(user);
+ }
+
+ public Inventory getInventory() {
+ return inventory;
+ }
+
+ public Map getItems() {
+ return items;
+ }
+
+ /**
+ * @return the listener
+ */
+ public Optional getListener() {
+ return Optional.ofNullable(listener);
+ }
+
+ public Optional getUser() {
+ return Optional.ofNullable(user);
+ }
+
+ public void open(Player... players) {
+ for (Player player : players) {
+ player.openInventory(inventory);
+ PanelListenerManager.getOpenPanels().put(player.getUniqueId(), this);
+ }
+ }
+
+ /**
+ * Open the inventory panel
+ * @param users - users that should see the panel
+ */
+ public void open(User... users) {
+ for (User u : users) {
+ u.getPlayer().openInventory(inventory);
+ PanelListenerManager.getOpenPanels().put(u.getUniqueId(), this);
+ }
+ }
+
+ /**
+ * @param inventory the inventory to set
+ */
+ public void setInventory(Inventory inventory) {
+ this.inventory = inventory;
+ }
+
+ /**
+ * @param items the items to set
+ */
+ public void setItems(Map items) {
+ this.items = items;
+ }
+
+ /**
+ * @param listener the listener to set
+ */
+ public void setListener(PanelListener listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * @param user - the User the user to set
+ */
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public void setHead(PanelItem item) {
+ // Update the panel item
+ items.values().stream().filter(i -> i.getName().equals(item.getName())).forEach(i -> i = item);
+ for (int i = 0; i < inventory.getSize(); i++) {
+ ItemStack it = inventory.getItem(i);
+ if (it != null && it.getType().equals(Material.SKULL_ITEM)) {
+ ItemMeta meta = it.getItemMeta();
+ if (item.getName().equals(meta.getLocalizedName())) {
+ inventory.setItem(i, item.getItem());
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/panels/PanelItem.java b/src/main/java/us/tastybento/bskyblock/api/panels/PanelItem.java
new file mode 100644
index 0000000..df27748
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/panels/PanelItem.java
@@ -0,0 +1,128 @@
+package us.tastybento.bskyblock.api.panels;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemFlag;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import us.tastybento.bskyblock.api.panels.builders.PanelItemBuilder;
+import us.tastybento.bskyblock.api.user.User;
+
+public class PanelItem {
+
+ public static PanelItem empty() {
+ return new PanelItemBuilder().build();
+ }
+
+ private ItemStack icon;
+ private ClickHandler clickHandler;
+ private List description;
+ private String name;
+ private boolean glow;
+ private ItemMeta meta;
+ private boolean playerHead;
+
+ public PanelItem(ItemStack icon, String name, List description, boolean glow, ClickHandler clickHandler, boolean playerHead) {
+ this.icon = icon;
+ this.playerHead = playerHead;
+ // Get the meta
+ meta = icon.getItemMeta();
+
+ this.clickHandler = clickHandler;
+
+ // Create the final item
+ setName(name);
+ setDescription(description);
+ setGlow(glow);
+
+ // Set flags to neaten up the view
+ meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
+ 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);
+ }
+
+ public ItemStack getItem() {
+ return icon;
+ }
+
+ public List getDescription() {
+ return description;
+ }
+
+ public void setDescription(List description) {
+ this.description = description;
+ meta.setLore(description);
+ icon.setItemMeta(meta);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ meta.setDisplayName(name);
+ meta.setLocalizedName(name); //Localized name cannot be overridden by the player using an anvils
+ icon.setItemMeta(meta);
+ }
+
+ public Optional getClickHandler() {
+ return Optional.ofNullable(clickHandler);
+ }
+
+ public boolean isGlow() {
+ return glow;
+ }
+
+ public void setGlow(boolean glow) {
+ this.glow = glow;
+ meta.addEnchant(Enchantment.ARROW_DAMAGE, 0, glow);
+ }
+
+ /**
+ * @return the playerHead
+ */
+ public boolean isPlayerHead() {
+ return playerHead;
+ }
+
+ /**
+ * Click handler interface
+ *
+ */
+ public interface ClickHandler {
+ /**
+ * This is executed when the icon is clicked
+ * @param panel - the panel that is being clicked
+ * @param user - the User
+ * @param clickType - the click type
+ * @param slot - the slot that was clicked
+ * @return true if the click event should be cancelled
+ */
+ boolean onClick(Panel panel, User user, ClickType clickType, int slot);
+ }
+
+ public void setHead(ItemStack itemStack) {
+ this.icon = itemStack;
+ // Get the meta
+ meta = icon.getItemMeta();
+ // Create the final item
+ setName(name);
+ setDescription(description);
+ setGlow(glow);
+
+ // Set flags to neaten up the view
+ meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
+ meta.addItemFlags(ItemFlag.HIDE_DESTROYS);
+ meta.addItemFlags(ItemFlag.HIDE_PLACED_ON);
+ meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
+ icon.setItemMeta(meta);
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/panels/PanelListener.java b/src/main/java/us/tastybento/bskyblock/api/panels/PanelListener.java
new file mode 100644
index 0000000..dbc7c59
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/panels/PanelListener.java
@@ -0,0 +1,19 @@
+package us.tastybento.bskyblock.api.panels;
+
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+
+import us.tastybento.bskyblock.api.user.User;
+
+public interface PanelListener {
+
+ /**
+ * This is called when the panel is first setup
+ */
+ void setup();
+
+ void onInventoryClose(InventoryCloseEvent event);
+
+ void onInventoryClick(User user, InventoryClickEvent event);
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/panels/builders/PanelBuilder.java b/src/main/java/us/tastybento/bskyblock/api/panels/builders/PanelBuilder.java
new file mode 100644
index 0000000..0a49fd3
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/panels/builders/PanelBuilder.java
@@ -0,0 +1,99 @@
+package us.tastybento.bskyblock.api.panels.builders;
+
+import java.util.TreeMap;
+
+import us.tastybento.bskyblock.api.panels.Panel;
+import us.tastybento.bskyblock.api.panels.PanelItem;
+import us.tastybento.bskyblock.api.panels.PanelListener;
+import us.tastybento.bskyblock.api.user.User;
+
+public class PanelBuilder {
+ private String name;
+ private TreeMap items = new TreeMap<>();
+ private int size;
+ private User user;
+ private PanelListener listener;
+
+ public PanelBuilder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Add item to the panel in the last slot.
+ * @param item - Panel item
+ * @return PanelBuilder
+ */
+ public PanelBuilder item(PanelItem item) {
+ return item(nextSlot(), item);
+ }
+
+ /**
+ * Add item into a specific slot. If it is already occupied, it will be replaced.
+ * @param slot - slot
+ * @param item - Panel item
+ * @return PanelBuilder
+ */
+ public PanelBuilder item(int slot, PanelItem item) {
+ items.put(slot, item);
+ return this;
+ }
+
+ /**
+ * Forces panel to be a specific number of slots.
+ * @param size - size to be
+ * @return PanelBuilder - PanelBuilder
+ */
+ public PanelBuilder size(int size) {
+ this.size = size;
+ return this;
+ }
+
+ /**
+ * Sets the user who will get this panel. This will open it immediately when it is built
+ * @param user - the User
+ * @return PanelBuilder
+ */
+ public PanelBuilder user(User user) {
+ this.user = user;
+ return this;
+ }
+
+ /**
+ * Sets which PanelListener will listen for clicks
+ * @param listener - listener for this panel
+ * @return PanelBuilder
+ */
+ public PanelBuilder listener(PanelListener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ /**
+ * Get the next free slot number
+ * @return next slot number, or -1 in case none has been found.
+ */
+ public int nextSlot() {
+ for (int i = 0 ; i < (size == 0 ? 54 : size) ; i++) {
+ if (!slotOccupied(i)) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Checks if a slot is occupied in the panel or not
+ * @param slot to check
+ * @return true or false
+ */
+ public boolean slotOccupied(int slot) {
+ return items.containsKey(slot);
+ }
+
+ /**
+ * Build the panel
+ * @return Panel
+ */
+ public Panel build() {
+ return new Panel(name, items, Math.max(size, items.isEmpty() ? size : items.lastKey()), user, listener);
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/panels/builders/PanelItemBuilder.java b/src/main/java/us/tastybento/bskyblock/api/panels/builders/PanelItemBuilder.java
new file mode 100644
index 0000000..54d173f
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/panels/builders/PanelItemBuilder.java
@@ -0,0 +1,98 @@
+package us.tastybento.bskyblock.api.panels.builders;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+
+import us.tastybento.bskyblock.api.panels.PanelItem;
+import us.tastybento.bskyblock.api.panels.PanelItem.ClickHandler;
+
+public class PanelItemBuilder {
+ private ItemStack icon = new ItemStack(Material.AIR);
+ private String name = "";
+ private List description = new ArrayList<>();
+ private boolean glow = false;
+ private PanelItem.ClickHandler clickHandler;
+ private boolean playerHead;
+
+ public PanelItemBuilder icon(Material icon) {
+ this.icon = new ItemStack(icon);
+ return this;
+ }
+
+ public PanelItemBuilder icon(ItemStack icon) {
+ this.icon = icon;
+ return this;
+ }
+
+ /**
+ * Set icon to player's head
+ * @param playerName - player's name
+ * @return PanelItemBuilder
+ */
+ public PanelItemBuilder icon(String playerName) {
+ this.icon = new ItemStack(Material.SKULL_ITEM, 1, (short) 3);
+ this.name = playerName;
+ this.playerHead = true;
+ return this;
+ }
+
+
+ public PanelItemBuilder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Adds a list of strings to the descriptions
+ * @param description - List of strings
+ * @return PanelItemBuilder
+ */
+ public PanelItemBuilder description(List description) {
+ this.description.addAll(description);
+ return this;
+ }
+
+ /**
+ * Add any number of lines to the description
+ * @param description strings of lines
+ * @return PanelItemBuilder
+ */
+ public PanelItemBuilder description(String... description) {
+ List additions = Arrays.asList(description);
+ ArrayList updatableList = new ArrayList<>();
+ updatableList.addAll(this.description);
+ updatableList.addAll(additions);
+ this.description = updatableList;
+ return this;
+ }
+
+ /**
+ * Adds a line to the description
+ * @param description - string
+ * @return PanelItemBuilder
+ */
+ public PanelItemBuilder description(String description) {
+ Collections.addAll(this.description, description.split("\n"));
+ return this;
+ }
+
+ public PanelItemBuilder glow(boolean glow) {
+ this.glow = glow;
+ return this;
+ }
+
+ public PanelItemBuilder clickHandler(ClickHandler clickHandler) {
+ this.clickHandler = clickHandler;
+ return this;
+ }
+
+ public PanelItem build() {
+ return new PanelItem(icon, name, description, glow, clickHandler, playerHead);
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/placeholders/Placeholder.java b/src/main/java/us/tastybento/bskyblock/api/placeholders/Placeholder.java
new file mode 100644
index 0000000..fc4addd
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/placeholders/Placeholder.java
@@ -0,0 +1,29 @@
+package us.tastybento.bskyblock.api.placeholders;
+
+import us.tastybento.bskyblock.api.user.User;
+
+/**
+ * @author Poslovitch
+ */
+public class Placeholder {
+
+ private String identifier;
+ private PlaceholderRequest request;
+
+ Placeholder(String identifier, PlaceholderRequest request) {
+ this.identifier = identifier;
+ this.request = request;
+ }
+
+ public String getIdentifier() {
+ return this.identifier;
+ }
+
+ public PlaceholderRequest getRequest() {
+ return request;
+ }
+
+ public interface PlaceholderRequest {
+ String request(User user);
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderAPIInterface.java b/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderAPIInterface.java
new file mode 100644
index 0000000..1bc6f10
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderAPIInterface.java
@@ -0,0 +1,39 @@
+package us.tastybento.bskyblock.api.placeholders;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.user.User;
+
+/**
+ * Simple interface for every Placeholder API.
+ *
+ * @author Poslovitch
+ */
+public interface PlaceholderAPIInterface {
+
+ /**
+ * Gets the name of the Placeholder API
+ * @return name of the placeholder plugin
+ */
+ String getName();
+
+ /**
+ * Registers the placeholder API
+ * @param plugin - BSkyBlock plugin object
+ * @return true if successfully registered
+ */
+ boolean register(BSkyBlock plugin);
+
+ /**
+ * Unregisters the placeholder API
+ * @param plugin - BSkyBlock plugin object
+ */
+ void unregister(BSkyBlock plugin);
+
+ /**
+ * Replace placeholders in the message according to the receiver
+ * @param receiver - user who will receive the message
+ * @param message - message
+ * @return updated message
+ */
+ String replacePlaceholders(User receiver, String message);
+}
\ No newline at end of file
diff --git a/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderBuilder.java b/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderBuilder.java
new file mode 100644
index 0000000..f6ca9f3
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderBuilder.java
@@ -0,0 +1,26 @@
+package us.tastybento.bskyblock.api.placeholders;
+
+public class PlaceholderBuilder {
+
+ private String identifier;
+ private Placeholder.PlaceholderRequest value;
+
+ public PlaceholderBuilder identifier(String identifier) {
+ this.identifier = identifier;
+ return this;
+ }
+
+ /**
+ * The value this placeholder should take
+ * @param value - placeholder request value
+ * @return PlaceholderBuilder object
+ */
+ public PlaceholderBuilder value(Placeholder.PlaceholderRequest value) {
+ this.value = value;
+ return this;
+ }
+
+ public Placeholder build() {
+ return new Placeholder(identifier, value);
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderHandler.java b/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderHandler.java
new file mode 100644
index 0000000..f69ba83
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/placeholders/PlaceholderHandler.java
@@ -0,0 +1,98 @@
+package us.tastybento.bskyblock.api.placeholders;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.user.User;
+
+/**
+ * Handles hooks with other Placeholder APIs.
+ *
+ * @author Poslovitch, Tastybento
+ */
+public class PlaceholderHandler {
+ private static final String PACKAGE = "us.tastybento.bskyblock.api.placeholders.hooks.";
+
+ // This class should never be instantiated (all methods are static)
+ private PlaceholderHandler() {}
+
+ /**
+ * List of API classes in the package specified above (except the Internal one)
+ */
+ private static final String[] HOOKS = {
+ //TODO
+ };
+
+ private static List apis = new ArrayList<>();
+
+ /**
+ * Register placeholders and hooks
+ * @param plugin - BSkyBlock plugin object
+ */
+ public static void register(BSkyBlock plugin){
+
+ // Load Internal Placeholder API
+ try{
+ Class> clazz = Class.forName(PACKAGE + "InternalPlaceholderImpl");
+ PlaceholderAPIInterface internal = (PlaceholderAPIInterface)clazz.newInstance();
+ apis.add(internal);
+ } catch (Exception e){
+ // Should never happen.
+ plugin.logError("Failed to load default placeholder API");
+ }
+
+ // Load hooks
+ for(String hook : HOOKS){
+ if(plugin.getServer().getPluginManager().isPluginEnabled(hook)){
+ try{
+ Class> clazz = Class.forName(PACKAGE + hook + "PlaceholderImpl");
+ PlaceholderAPIInterface api = (PlaceholderAPIInterface)clazz.newInstance();
+ if(api.register(plugin)){
+ plugin.log("Hooked placeholders into " + hook); // since Java 8, we can use Supplier , which will be evaluated lazily
+ apis.add(api);
+ } else {
+ plugin.log("Failed to hook placeholders into " + hook);
+ }
+ } catch (Exception e){
+ plugin.log("Failed to hook placeholders into " + hook);
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister placeholder hooks
+ * @param plugin - BSkyBlock plugin object
+ */
+ public static void unregister(BSkyBlock plugin){
+ Iterator it = apis.iterator();
+ while (it.hasNext()) {
+ PlaceholderAPIInterface api = it.next();
+ api.unregister(plugin);
+ it.remove();
+ }
+ }
+
+ /**
+ * Replace placeholders in the message according to the receiver
+ * @param receiver - user to receive the message
+ * @param message - message
+ * @return updated message
+ */
+ public static String replacePlaceholders(User receiver, String message){
+ for(PlaceholderAPIInterface api : apis){
+ message = api.replacePlaceholders(receiver, message);
+ }
+
+ return message;
+ }
+
+ /**
+ * @return true if APIs are registered (including Internal), otherwise false
+ */
+ public static boolean hasHooks(){
+ return apis != null;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/placeholders/hooks/InternalPlaceholderImpl.java b/src/main/java/us/tastybento/bskyblock/api/placeholders/hooks/InternalPlaceholderImpl.java
new file mode 100644
index 0000000..f8afcad
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/placeholders/hooks/InternalPlaceholderImpl.java
@@ -0,0 +1,45 @@
+package us.tastybento.bskyblock.api.placeholders.hooks;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.placeholders.Placeholder;
+import us.tastybento.bskyblock.api.placeholders.PlaceholderAPIInterface;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.lists.Placeholders;
+
+/**
+ * Built-in placeholder API
+ *
+ * @author Poslovitch
+ */
+public class InternalPlaceholderImpl implements PlaceholderAPIInterface {
+
+ @Override
+ public String getName() {
+ return "Internal";
+ }
+
+ @Override
+ public boolean register(BSkyBlock plugin) {
+ return true;
+ }
+
+ @Override
+ public void unregister(BSkyBlock plugin) {
+ // Useless : it would disable the placeholders.
+ }
+
+ @Override
+ public String replacePlaceholders(User receiver, String message) {
+ if(message == null || message.isEmpty()) {
+ return "";
+ }
+
+ for(Placeholder placeholder : Placeholders.values()){
+ String identifier = "%" + placeholder.getIdentifier() + "%";
+ message = message.replaceAll(identifier, placeholder.getRequest().request(receiver));
+ }
+
+ return message;
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/user/Notifier.java b/src/main/java/us/tastybento/bskyblock/api/user/Notifier.java
new file mode 100644
index 0000000..7209489
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/user/Notifier.java
@@ -0,0 +1,71 @@
+package us.tastybento.bskyblock.api.user;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+/**
+ * Utilities class that helps to avoid spamming the User with potential repeated messages
+ * @author Poslovitch
+ */
+public class Notifier {
+
+ /**
+ * Time in seconds before {@link #notificationCache} removes the entry related to the player.
+ */
+ private static final int NOTIFICATION_DELAY = 4;
+
+ private final LoadingCache notificationCache = CacheBuilder.newBuilder()
+ .expireAfterAccess(NOTIFICATION_DELAY, TimeUnit.SECONDS)
+ .maximumSize(500)
+ .build(
+ new CacheLoader() {
+ @Override
+ public Notification load(User user) {
+ return new Notification(null, 0);
+ }
+ }
+ );
+
+ /**
+ * Sends message to a user only if the message hasn't been sent recently
+ * @param user - user
+ * @param message - message to send (already translated)
+ * @return true if message sent successfully, false it it has been throttled
+ */
+ public synchronized boolean notify(User user, String message) {
+ try {
+ Notification lastNotification = notificationCache.get(user);
+ long now = System.currentTimeMillis();
+ if (now >= lastNotification.getTime() + NOTIFICATION_DELAY * 1000 || !message.equals(lastNotification.getMessage())) {
+ notificationCache.put(user, new Notification(message, now));
+ user.sendRawMessage(message);
+ return true;
+ }
+ return false;
+ } catch (ExecutionException e) {
+ return false;
+ }
+ }
+
+ public class Notification {
+ private final String message;
+ private final long time;
+
+ public Notification(String message, long time) {
+ this.message = message;
+ this.time = time;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public long getTime() {
+ return time;
+ }
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/api/user/User.java b/src/main/java/us/tastybento/bskyblock/api/user/User.java
new file mode 100644
index 0000000..e7acff6
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/api/user/User.java
@@ -0,0 +1,374 @@
+package us.tastybento.bskyblock.api.user;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.GameMode;
+import org.bukkit.Location;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.World;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.PlayerInventory;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+
+import us.tastybento.bskyblock.BSkyBlock;
+import us.tastybento.bskyblock.api.placeholders.PlaceholderHandler;
+
+/**
+ * BSB's user object. Wraps Player.
+ * @author tastybento
+ *
+ */
+public class User {
+
+ private static Map users = new HashMap<>();
+
+ /**
+ * Clears all users from the user list
+ */
+ public static void clearUsers() {
+ users.clear();
+ }
+
+ /**
+ * Get an instance of User from a CommandSender
+ * @param sender - command sender, e.g. console
+ * @return user - user
+ */
+ public static User getInstance(CommandSender sender) {
+ if (sender instanceof Player) {
+ return getInstance((Player)sender);
+ }
+ // Console
+ return new User(sender);
+ }
+ /**
+ * Get an instance of User from a Player object
+ * @param player - the player
+ * @return user - user
+ */
+ public static User getInstance(Player player) {
+ if (player == null) {
+ return null;
+ }
+ if (users.containsKey(player.getUniqueId())) {
+ return users.get(player.getUniqueId());
+ }
+ return new User(player);
+ }
+ /**
+ * Get an instance of User from a UUID
+ * @param uuid - UUID
+ * @return user - user
+ */
+ public static User getInstance(UUID uuid) {
+ if (uuid == null) {
+ return null;
+ }
+ if (users.containsKey(uuid)) {
+ return users.get(uuid);
+ }
+ // Return player, or null if they are not online
+ return new User(uuid);
+ }
+ /**
+ * Removes this player from the User cache
+ * @param player - the player
+ */
+ public static void removePlayer(Player player) {
+ users.remove(player.getUniqueId());
+ }
+
+ // ----------------------------------------------------
+
+ private static BSkyBlock plugin = BSkyBlock.getInstance();
+
+ private Player player;
+ private final UUID playerUUID;
+ private final CommandSender sender;
+
+ private User(CommandSender sender) {
+ player = null;
+ playerUUID = null;
+ this.sender = sender;
+ }
+
+ private User(Player player) {
+ this.player = player;
+ sender = player;
+ playerUUID = player.getUniqueId();
+ users.put(player.getUniqueId(), this);
+ }
+
+ private User(UUID playerUUID) {
+ player = Bukkit.getPlayer(playerUUID);
+ this.playerUUID = playerUUID;
+ sender = player;
+ }
+
+ /**
+ * Used for testing
+ * @param p - BSkyBlock plugin
+ */
+ public static void setPlugin(BSkyBlock p) {
+ plugin = p;
+ }
+
+ public Set getEffectivePermissions() {
+ return sender.getEffectivePermissions();
+ }
+
+ public PlayerInventory getInventory() {
+ return player != null ? player.getInventory() : null;
+ }
+
+ public Location getLocation() {
+ return player != null ? player.getLocation() : null;
+ }
+
+ public String getName() {
+ return player != null ? player.getName() : plugin.getPlayers().getName(playerUUID);
+ }
+
+ /**
+ * @return the player
+ */
+ public Player getPlayer() {
+ return player;
+ }
+
+ /**
+ * @return true if this user is a player, false if not, e.g., console
+ */
+ public boolean isPlayer() {
+ return player != null;
+ }
+
+ public CommandSender getSender() {
+ return sender;
+ }
+
+ public UUID getUniqueId() {
+ return playerUUID;
+ }
+
+ /**
+ * @param permission - permission string
+ * @return true if permission is empty or if the player has that permission
+ */
+ public boolean hasPermission(String permission) {
+ return permission.isEmpty() || sender.hasPermission(permission);
+ }
+
+ public boolean isOnline() {
+ return player != null && player.isOnline();
+ }
+
+ /**
+ * Checks if user is Op
+ * @return true if user is Op
+ */
+ public boolean isOp() {
+ if (sender != null) {
+ return sender.isOp();
+ }
+ if (playerUUID != null) {
+ OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerUUID);
+ if (offlinePlayer != null) {
+ return offlinePlayer.isOp();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets a translation of this reference for this user.
+ * @param reference - reference found in a locale file
+ * @param variables - variables to insert into translated string. Variables go in pairs, for example
+ * "[name]", "tastybento"
+ * @return Translated string with colors converted, or the reference if nothing has been found
+ */
+ public String getTranslation(String reference, String... variables) {
+ // Get translation
+ String translation = plugin.getLocalesManager().get(this, reference);
+
+ // If no translation has been found, return the reference for debug purposes.
+ if (translation == null) {
+ return reference;
+ }
+
+ // Then replace variables
+ if (variables.length > 1) {
+ for (int i = 0; i < variables.length; i += 2) {
+ translation = translation.replace(variables[i], variables[i+1]);
+ }
+ }
+
+ // Replace placeholders
+ translation = PlaceholderHandler.replacePlaceholders(this, translation);
+
+ return ChatColor.translateAlternateColorCodes('&', translation);
+ }
+
+ /**
+ * Gets a translation of this reference for this user.
+ * @param reference - reference found in a locale file
+ * @param variables - variables to insert into translated string. Variables go in pairs, for example
+ * "[name]", "tastybento"
+ * @return Translated string with colors converted, or a blank String if nothing has been found
+ */
+ public String getTranslationOrNothing(String reference, String... variables) {
+ String translation = getTranslation(reference, variables);
+ return translation.equals(reference) ? "" : translation;
+ }
+
+ /**
+ * Send a message to sender if message is not empty.
+ * @param reference - language file reference
+ * @param variables - CharSequence target, replacement pairs
+ */
+ public void sendMessage(String reference, String... variables) {
+ String message = getTranslation(reference, variables);
+ if (!ChatColor.stripColor(message).trim().isEmpty()) {
+ if (sender != null) {
+ sender.sendMessage(message);
+ } else {
+ // TODO: Offline message
+ // Save this message so the player can see it later
+ }
+ }
+ }
+
+ /**
+ * Sends a message to sender without any modification (colors, multi-lines, placeholders).
+ * @param message - the message to send
+ */
+ public void sendRawMessage(String message) {
+ if (sender != null) {
+ sender.sendMessage(message);
+ } else {
+ // TODO: Offline message
+ // Save this message so the player can see it later
+ }
+ }
+
+ /**
+ * Sends a message to sender if message is not empty and if the same wasn't sent within the previous {@link Notifier#NOTIFICATION_DELAY} seconds.
+ * @param reference - language file reference
+ * @param variables - CharSequence target, replacement pairs
+ *
+ * @see Notifier
+ */
+ public void notify(String reference, String... variables) {
+ String message = getTranslation(reference, variables);
+ if (!ChatColor.stripColor(message).trim().isEmpty() && sender != null) {
+ plugin.getNotifier().notify(this, message);
+ }
+ }
+
+ /**
+ * Sets the user's game mode
+ * @param mode - GameMode
+ */
+ public void setGameMode(GameMode mode) {
+ player.setGameMode(mode);
+ }
+
+ /**
+ * Teleports user to this location. If the user is in a vehicle, they will exit first.
+ * @param location - the location
+ */
+ public void teleport(Location location) {
+ player.teleport(location);
+ }
+
+ /**
+ * Gets the current world this entity resides in
+ * @return World
+ */
+ public World getWorld() {
+ return player.getWorld();
+ }
+
+ /**
+ * Closes the user's inventory
+ */
+ public void closeInventory() {
+ player.closeInventory();
+ }
+
+ /**
+ * Get the user's locale
+ * @return Locale
+ */
+ public Locale getLocale() {
+ if (sender instanceof Player && !plugin.getPlayers().getLocale(playerUUID).isEmpty()) {
+ return Locale.forLanguageTag(plugin.getPlayers().getLocale(playerUUID));
+ }
+ return Locale.forLanguageTag(plugin.getSettings().getDefaultLanguage());
+
+ }
+
+ /**
+ * Forces an update of the user's complete inventory.
+ * Deprecated, but there is no current alternative.
+ */
+ public void updateInventory() {
+ player.updateInventory();
+
+ }
+
+ /**
+ * Performs a command as the player
+ * @param cmd - command to execute
+ * @return true if the command was successful, otherwise false
+ */
+ public boolean performCommand(String cmd) {
+ return player.performCommand(cmd);
+
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((playerUUID == null) ? 0 : playerUUID.hashCode());
+ return result;
+ }
+ /* (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 User)) {
+ return false;
+ }
+ User other = (User) obj;
+ if (playerUUID == null) {
+ return other.playerUUID == null;
+ } else return playerUUID.equals(other.playerUUID);
+ }
+
+ /**
+ * Checks if a user is in one of the game worlds
+ * @return true if user is, false if not
+ */
+ public boolean inWorld() {
+ return plugin.getIWM().inWorld(getLocation());
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/commands/AdminCommand.java b/src/main/java/us/tastybento/bskyblock/commands/AdminCommand.java
new file mode 100755
index 0000000..456f28e
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/commands/AdminCommand.java
@@ -0,0 +1,74 @@
+package us.tastybento.bskyblock.commands;
+
+import java.util.List;
+
+import us.tastybento.bskyblock.api.commands.CompositeCommand;
+import us.tastybento.bskyblock.api.localization.TextVariables;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.commands.admin.AdminClearResetsAllCommand;
+import us.tastybento.bskyblock.commands.admin.AdminClearResetsCommand;
+import us.tastybento.bskyblock.commands.admin.AdminGetRankCommand;
+import us.tastybento.bskyblock.commands.admin.AdminInfoCommand;
+import us.tastybento.bskyblock.commands.admin.AdminRegisterCommand;
+import us.tastybento.bskyblock.commands.admin.AdminReloadCommand;
+import us.tastybento.bskyblock.commands.admin.AdminSchemCommand;
+import us.tastybento.bskyblock.commands.admin.AdminSetRankCommand;
+import us.tastybento.bskyblock.commands.admin.AdminTeleportCommand;
+import us.tastybento.bskyblock.commands.admin.AdminUnregisterCommand;
+import us.tastybento.bskyblock.commands.admin.AdminVersionCommand;
+import us.tastybento.bskyblock.commands.admin.range.AdminRangeCommand;
+import us.tastybento.bskyblock.commands.admin.team.AdminTeamAddCommand;
+import us.tastybento.bskyblock.commands.admin.team.AdminTeamDisbandCommand;
+import us.tastybento.bskyblock.commands.admin.team.AdminTeamKickCommand;
+import us.tastybento.bskyblock.commands.admin.team.AdminTeamMakeLeaderCommand;
+
+public class AdminCommand extends CompositeCommand {
+
+ public AdminCommand() {
+ super("bsbadmin", "bsb");
+ }
+
+ @Override
+ public void setup() {
+ setPermissionPrefix("bskyblock");
+ setPermission("admin.*");
+ setOnlyPlayer(false);
+ setParameters("commands.admin.help.parameters");
+ setDescription("commands.admin.help.description");
+ setWorld(getPlugin().getIWM().getBSBIslandWorld());
+ new AdminVersionCommand(this);
+ new AdminReloadCommand(this);
+ new AdminTeleportCommand(this, "tp");
+ new AdminTeleportCommand(this, "tpnether");
+ new AdminTeleportCommand(this, "tpend");
+ new AdminGetRankCommand(this);
+ new AdminSetRankCommand(this);
+ new AdminInfoCommand(this);
+ // Team commands
+ new AdminTeamAddCommand(this);
+ new AdminTeamKickCommand(this);
+ new AdminTeamDisbandCommand(this);
+ new AdminTeamMakeLeaderCommand(this);
+ // Schems
+ new AdminSchemCommand(this);
+ // Register/unregister islands
+ new AdminRegisterCommand(this);
+ new AdminUnregisterCommand(this);
+ // Range
+ new AdminRangeCommand(this);
+ // Resets
+ new AdminClearResetsCommand(this);
+ new AdminClearResetsAllCommand(this);
+ }
+
+ @Override
+ public boolean execute(User user, String label, List args) {
+ if (!args.isEmpty()) {
+ user.sendMessage("general.errors.unknown-command", TextVariables.LABEL, getTopLabel());
+ return false;
+ }
+ // By default run the attached help command, if it exists (it should)
+ return showHelp(this, user);
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/commands/IslandCommand.java b/src/main/java/us/tastybento/bskyblock/commands/IslandCommand.java
new file mode 100755
index 0000000..d7cfac9
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/commands/IslandCommand.java
@@ -0,0 +1,78 @@
+package us.tastybento.bskyblock.commands;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import us.tastybento.bskyblock.api.commands.CompositeCommand;
+import us.tastybento.bskyblock.api.localization.TextVariables;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.commands.island.IslandAboutCommand;
+import us.tastybento.bskyblock.commands.island.IslandBanCommand;
+import us.tastybento.bskyblock.commands.island.IslandBanlistCommand;
+import us.tastybento.bskyblock.commands.island.IslandCreateCommand;
+import us.tastybento.bskyblock.commands.island.IslandGoCommand;
+import us.tastybento.bskyblock.commands.island.IslandLanguageCommand;
+import us.tastybento.bskyblock.commands.island.IslandResetCommand;
+import us.tastybento.bskyblock.commands.island.IslandResetnameCommand;
+import us.tastybento.bskyblock.commands.island.IslandSethomeCommand;
+import us.tastybento.bskyblock.commands.island.IslandSetnameCommand;
+import us.tastybento.bskyblock.commands.island.IslandSettingsCommand;
+import us.tastybento.bskyblock.commands.island.IslandUnbanCommand;
+import us.tastybento.bskyblock.commands.island.team.IslandTeamCommand;
+
+public class IslandCommand extends CompositeCommand {
+
+ public IslandCommand() {
+ super("island", "is");
+ }
+
+ /* (non-Javadoc)
+ * @see us.tastybento.bskyblock.api.commands.CompositeCommand#setup()
+ */
+ @Override
+ public void setup() {
+ setDescription("commands.island.help.description");
+ setOnlyPlayer(true);
+ // Permission
+ setPermissionPrefix("bskyblock");
+ setPermission("island");
+ setWorld(getPlugin().getIWM().getBSBIslandWorld());
+ // Set up subcommands
+ new IslandAboutCommand(this);
+ new IslandCreateCommand(this);
+ new IslandGoCommand(this);
+ new IslandResetCommand(this);
+ new IslandSetnameCommand(this);
+ new IslandResetnameCommand(this);
+ new IslandSethomeCommand(this);
+ new IslandSettingsCommand(this);
+ new IslandLanguageCommand(this);
+ new IslandBanCommand(this);
+ new IslandUnbanCommand(this);
+ new IslandBanlistCommand(this);
+ // Team commands
+ new IslandTeamCommand(this);
+ }
+
+ /* (non-Javadoc)
+ * @see us.tastybento.bskyblock.api.commands.CommandArgument#execute(org.bukkit.command.CommandSender, java.lang.String[])
+ */
+ @Override
+ public boolean execute(User user, String label, List args) {
+ if (user == null) {
+ return false;
+ }
+ if (args.isEmpty()) {
+ // If user has an island, go
+ if (getPlugin().getIslands().getIsland(getWorld(), user.getUniqueId()) != null) {
+ return getSubCommand("go").map(goCmd -> goCmd.execute(user, goCmd.getLabel(), new ArrayList<>())).orElse(false);
+ }
+ // No islands currently
+ return getSubCommand("create").map(createCmd -> createCmd.execute(user, createCmd.getLabel(), new ArrayList<>())).orElse(false);
+ }
+ user.sendMessage("general.errors.unknown-command", TextVariables.LABEL, getTopLabel());
+ return false;
+
+ }
+
+}
diff --git a/src/main/java/us/tastybento/bskyblock/commands/admin/AdminClearResetsAllCommand.java b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminClearResetsAllCommand.java
new file mode 100644
index 0000000..05efa4f
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminClearResetsAllCommand.java
@@ -0,0 +1,40 @@
+package us.tastybento.bskyblock.commands.admin;
+
+import java.util.List;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+import us.tastybento.bskyblock.api.commands.CompositeCommand;
+import us.tastybento.bskyblock.api.user.User;
+
+public class AdminClearResetsAllCommand extends CompositeCommand {
+
+ public AdminClearResetsAllCommand(CompositeCommand parent) {
+ super(parent, "clearresetsall");
+ }
+
+ @Override
+ public void setup() {
+ setPermission("admin.clearresetsall");
+ setDescription("commands.admin.clearresetsall.description");
+ }
+
+ @Override
+ public boolean execute(User user, String label, List args) {
+ // If args are not right, show help
+ if (!args.isEmpty()) {
+ showHelp(this, user);
+ return false;
+ }
+ this.askConfirmation(user, () -> {
+ // Set the reset epoch to now
+ getIWM().setResetEpoch(getWorld());
+ // Reset all current players
+ Bukkit.getOnlinePlayers().stream().map(Player::getUniqueId).filter(getPlayers()::isKnown).forEach(u -> getPlayers().setResets(getWorld(), u, 0));
+ user.sendMessage("general.success");
+ });
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/us/tastybento/bskyblock/commands/admin/AdminClearResetsCommand.java b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminClearResetsCommand.java
new file mode 100644
index 0000000..7fc7bf3
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminClearResetsCommand.java
@@ -0,0 +1,59 @@
+package us.tastybento.bskyblock.commands.admin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import us.tastybento.bskyblock.api.commands.CompositeCommand;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.util.Util;
+
+public class AdminClearResetsCommand extends CompositeCommand {
+
+ public AdminClearResetsCommand(CompositeCommand parent) {
+ super(parent, "clearresets");
+ }
+
+ @Override
+ public void setup() {
+ setPermission("admin.clearreset");
+ setParameters("commands.admin.clearresets.parameters");
+ setDescription("commands.admin.clearresets.description");
+ }
+
+ @Override
+ public boolean execute(User user, String label, List args) {
+ // If args are not right, show help
+ if (args.size() != 1) {
+ showHelp(this, user);
+ return false;
+ }
+ // Get target
+ UUID targetUUID = getPlayers().getUUID(args.get(0));
+ if (targetUUID == null) {
+ user.sendMessage("general.errors.unknown-player");
+ return false;
+ }
+ if (!getIslands().hasIsland(getWorld(), targetUUID)) {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+ // Clear resets
+ user.sendMessage("commands.admin.clearresets.cleared");
+ getPlayers().setResets(getWorld(), targetUUID, 0);
+ user.sendMessage("general.success");
+ return true;
+ }
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List 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 options = new ArrayList<>(Util.getOnlinePlayerList(user));
+ return Optional.of(Util.tabLimit(options, lastArg));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/us/tastybento/bskyblock/commands/admin/AdminGetRankCommand.java b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminGetRankCommand.java
new file mode 100644
index 0000000..ab38bf8
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminGetRankCommand.java
@@ -0,0 +1,71 @@
+package us.tastybento.bskyblock.commands.admin;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+import us.tastybento.bskyblock.api.commands.CompositeCommand;
+import us.tastybento.bskyblock.api.localization.TextVariables;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.database.objects.Island;
+import us.tastybento.bskyblock.managers.RanksManager;
+
+/**
+ * @author tastybento
+ *
+ */
+public class AdminGetRankCommand extends CompositeCommand {
+
+ public AdminGetRankCommand(CompositeCommand adminCommand) {
+ super(adminCommand, "getrank");
+ }
+
+ /* (non-Javadoc)
+ * @see us.tastybento.bskyblock.api.commands.BSBCommand#setup()
+ */
+ @Override
+ public void setup() {
+ setPermission("admin.setrank");
+ setOnlyPlayer(false);
+ setParameters("commands.admin.getrank.parameters");
+ setDescription("commands.admin.getrank.description");
+ }
+
+ /* (non-Javadoc)
+ * @see us.tastybento.bskyblock.api.commands.BSBCommand#execute(us.tastybento.bskyblock.api.user.User, java.util.List)
+ */
+ @Override
+ public boolean execute(User user, String label, List args) {
+ if (args.size() != 1) {
+ // Show help
+ showHelp(this, user);
+ return false;
+ }
+ // Get target player
+ UUID targetUUID = getPlayers().getUUID(args.get(0));
+ if (targetUUID == null) {
+ user.sendMessage("general.errors.unknown-player");
+ return false;
+ }
+ if (!getIslands().hasIsland(getWorld(), targetUUID)) {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+ // Get rank
+ RanksManager rm = getPlugin().getRanksManager();
+ User target = User.getInstance(targetUUID);
+ Island island = getIslands().getIsland(getWorld(), targetUUID);
+ int currentRank = island.getRank(target);
+ user.sendMessage("commands.admin.getrank.rank-is", TextVariables.RANK, user.getTranslation(rm.getRank(currentRank)));
+ return true;
+ }
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List args) {
+ return Optional.of(Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()));
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/commands/admin/AdminInfoCommand.java b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminInfoCommand.java
new file mode 100644
index 0000000..991391a
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminInfoCommand.java
@@ -0,0 +1,52 @@
+package us.tastybento.bskyblock.commands.admin;
+
+import java.util.List;
+import java.util.UUID;
+
+import us.tastybento.bskyblock.api.commands.CompositeCommand;
+import us.tastybento.bskyblock.api.user.User;
+
+public class AdminInfoCommand extends CompositeCommand {
+
+ public AdminInfoCommand(CompositeCommand parent) {
+ super(parent, "info");
+ }
+
+ @Override
+ public void setup() {
+ setPermission("admin.info");
+ setOnlyPlayer(false);
+ setParameters("commands.admin.info.parameters");
+ setDescription("commands.admin.info.description");
+ }
+
+ @Override
+ public boolean execute(User user, String label, List args) {
+ if (args.size() > 1 || (args.isEmpty() && !user.isPlayer())) {
+ // Show help
+ showHelp(this, user);
+ return false;
+ }
+ // If there are no args, then the player wants info on the island at this location
+ if (args.isEmpty()) {
+ if (!getIslands().getIslandAt(user.getLocation()).map(i -> i.showInfo(getPlugin(), user, getWorld())).orElse(false)) {
+ user.sendMessage("commands.admin.info.no-island");
+ return false;
+ }
+ return true;
+ }
+ // Get target player
+ UUID targetUUID = getPlayers().getUUID(args.get(0));
+ if (targetUUID == null) {
+ user.sendMessage("general.errors.unknown-player");
+ return false;
+ }
+ if (!getIslands().hasIsland(getWorld(), targetUUID)) {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+ // Show info for this player
+ getIslands().getIsland(getWorld(), targetUUID).showInfo(getPlugin(), user, getWorld());
+ return true;
+ }
+}
diff --git a/src/main/java/us/tastybento/bskyblock/commands/admin/AdminRegisterCommand.java b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminRegisterCommand.java
new file mode 100644
index 0000000..356dd70
--- /dev/null
+++ b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminRegisterCommand.java
@@ -0,0 +1,106 @@
+package us.tastybento.bskyblock.commands.admin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.bukkit.Location;
+import org.bukkit.Material;
+
+import us.tastybento.bskyblock.api.commands.CompositeCommand;
+import us.tastybento.bskyblock.api.user.User;
+import us.tastybento.bskyblock.database.objects.Island;
+import us.tastybento.bskyblock.util.Util;
+
+public class AdminRegisterCommand extends CompositeCommand {
+
+ public AdminRegisterCommand(CompositeCommand parent) {
+ super(parent, "register");
+ }
+
+ @Override
+ public void setup() {
+ setPermission("admin.register");
+ setOnlyPlayer(true);
+ setParameters("commands.admin.register.parameters");
+ setDescription("commands.admin.register.description");
+ }
+
+ @Override
+ public boolean execute(User user, String label, List args) {
+ // If args are not right, show help
+ if (args.size() != 1) {
+ showHelp(this, user);
+ return false;
+ }
+ // Get target
+ UUID targetUUID = getPlayers().getUUID(args.get(0));
+ if (targetUUID == null) {
+ user.sendMessage("general.errors.unknown-player");
+ return false;
+ }
+ if (getIslands().hasIsland(getWorld(), targetUUID)) {
+ user.sendMessage("general.errors.player-has-island");
+ return false;
+ }
+ if (getIslands().inTeam(getWorld(), targetUUID)) {
+ user.sendMessage("commands.admin.register.cannot-register-team-player");
+ return false;
+ }
+
+ // Check if island is owned
+ Optional