Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop

This commit is contained in:
tastybento 2021-02-16 17:15:19 -08:00
commit 969b413588
15 changed files with 391 additions and 97 deletions

View File

@ -138,12 +138,28 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "panel.filler-material", since = "1.14.0")
private Material panelFillerMaterial = Material.LIGHT_BLUE_STAINED_GLASS_PANE;
@ConfigComment("Toggle whether player head texture should be gathered from Mojang API or mc-heads.net cache server.")
@ConfigComment("Mojang API sometime may be slow and may limit requests to the player data, so this will allow to")
@ConfigComment("get player heads a bit faster then Mojang API.")
@ConfigEntry(path = "panel.use-cache-server", since = "1.16.0")
private boolean useCacheServer = false;
@ConfigComment("Defines how long player skin texture link is stored into local cache before it is requested again.")
@ConfigComment("Defined value is in the minutes.")
@ConfigComment("Value 0 will not clear cache until server restart.")
@ConfigEntry(path = "panel.head-cache-time", since = "1.14.1")
private long playerHeadCacheTime = 60;
@ConfigComment("Defines a number of player heads requested per tasks.")
@ConfigComment("Setting it too large may lead to temporarily being blocked from head gatherer API.")
@ConfigEntry(path = "panel.heads-per-call", since = "1.16.0")
private int headsPerCall = 9;
@ConfigComment("Defines a number of ticks between each player head request task.")
@ConfigComment("Setting it too large may lead to temporarily being blocked from head gatherer API.")
@ConfigEntry(path = "panel.ticks-between-calls", since = "1.16.0", needsRestart = true)
private long ticksBetweenCalls = 10;
/*
* Logs
*/
@ -783,4 +799,76 @@ public class Settings implements ConfigObject {
{
this.playerHeadCacheTime = playerHeadCacheTime;
}
/**
* Is use cache server boolean.
*
* @return the boolean
* @since 1.16.0
*/
public boolean isUseCacheServer()
{
return useCacheServer;
}
/**
* Sets use cache server.
*
* @param useCacheServer the use cache server
* @since 1.16.0
*/
public void setUseCacheServer(boolean useCacheServer)
{
this.useCacheServer = useCacheServer;
}
/**
* Gets heads per call.
*
* @return the heads per call
* @since 1.16.0
*/
public int getHeadsPerCall()
{
return headsPerCall;
}
/**
* Sets heads per call.
*
* @param headsPerCall the heads per call
* @since 1.16.0
*/
public void setHeadsPerCall(int headsPerCall)
{
this.headsPerCall = headsPerCall;
}
/**
* Gets ticks between calls.
*
* @return the ticks between calls
* @since 1.16.0
*/
public long getTicksBetweenCalls()
{
return ticksBetweenCalls;
}
/**
* Sets ticks between calls.
*
* @param ticksBetweenCalls the ticks between calls
* @since 1.16.0
*/
public void setTicksBetweenCalls(long ticksBetweenCalls)
{
this.ticksBetweenCalls = ticksBetweenCalls;
}
}

View File

@ -78,9 +78,9 @@ public class AdminSettingsCommand extends CompositeCommand {
// Player settings
new TabbedPanelBuilder()
.user(user)
.world(getWorld())
.tab(1, new SettingsTab(getWorld(), user, island, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(getWorld(), user, island, Flag.Type.SETTING))
.world(island.getWorld())
.tab(1, new SettingsTab(user, island, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(user, island, Flag.Type.SETTING))
.startingSlot(1)
.size(54)
.build().openPanel();

View File

@ -15,6 +15,8 @@ import world.bentobox.bentobox.util.Util;
*/
public class IslandSettingsCommand extends CompositeCommand {
private Island island;
public IslandSettingsCommand(CompositeCommand islandCommand) {
super(islandCommand, "settings", "flags", "options");
}
@ -28,23 +30,26 @@ public class IslandSettingsCommand extends CompositeCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
// Settings are only shown if you are in the right world
if (Util.getWorld(user.getWorld()).equals(getWorld())) {
return true;
// Player is in same world
island = getIslands().getIslandAt(user.getLocation()).orElseGet(() -> getIslands().getIsland(user.getWorld(), user.getUniqueId()));
} else {
user.sendMessage("general.errors.wrong-world");
island = getIslands().getIsland(getWorld(), user);
}
if (island == null) {
user.sendMessage("general.errors.no-island");
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
Island island = getIslands().getIslandAt(user.getLocation()).orElseGet(() -> getIslands().getIsland(user.getWorld(), user.getUniqueId()));
new TabbedPanelBuilder()
.user(user)
.world(getWorld())
.tab(1, new SettingsTab(getWorld(), user, island, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(getWorld(), user, island, Flag.Type.SETTING))
.world(island.getWorld())
.tab(1, new SettingsTab(user, island, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(user, island, Flag.Type.SETTING))
.startingSlot(1)
.size(54)
.hideIfEmpty()

View File

@ -4,12 +4,14 @@ import java.util.Map;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.listeners.PanelListenerManager;
import world.bentobox.bentobox.util.heads.HeadGetter;
@ -27,6 +29,7 @@ public class Panel implements HeadRequester, InventoryHolder {
private PanelListener listener;
private User user;
private String name;
private World world;
/**
* Various types of Panel that can be created.
@ -51,6 +54,17 @@ public class Panel implements HeadRequester, InventoryHolder {
makePanel(name, items, size, user, listener, type);
}
/**
* @param pb - PanelBuilder
* @since 1.16.0
*/
public Panel(PanelBuilder pb) {
this.world = pb.getWorld();
this.makePanel(pb.getName(), pb.getItems(),
Math.max(pb.getSize(), pb.getItems().isEmpty() ? pb.getSize() : pb.getItems().lastKey() + 1),
pb.getUser(), pb.getListener(), pb.getPanelType());
}
protected void makePanel(String name, Map<Integer, PanelItem> items, int size, User user,
PanelListener listener) {
this.makePanel(name, items, size, user, listener, Type.INVENTORY);
@ -200,4 +214,23 @@ public class Panel implements HeadRequester, InventoryHolder {
public String getName() {
return name;
}
/**
* Get the world that applies to this panel
* @return the optional world
* @since 1.16.0
*/
public Optional<World> getWorld() {
return Optional.ofNullable(world);
}
/**
* @param world the world to set
* @since 1.16.0
*/
public void setWorld(World world) {
this.world = world;
}
}

View File

@ -42,6 +42,7 @@ public class TabbedPanel extends Panel implements PanelListener {
*/
public TabbedPanel(TabbedPanelBuilder tpb) {
this.tpb = tpb;
this.setWorld(tpb.getWorld());
}
/* (non-Javadoc)

View File

@ -4,6 +4,7 @@ import java.util.SortedMap;
import java.util.TreeMap;
import org.bukkit.ChatColor;
import org.bukkit.World;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
@ -22,6 +23,7 @@ public class PanelBuilder {
private User user;
private PanelListener listener;
private Panel.Type type = Panel.Type.INVENTORY;
private World world;
public PanelBuilder name(String name) {
this.name = ChatColor.translateAlternateColorCodes('&', name);
@ -115,13 +117,22 @@ public class PanelBuilder {
return items.containsKey(slot);
}
/**
* Set the game world that applies this panel
* @param world
* @return PanelBuilder
*/
public PanelBuilder world(World world) {
this.world = world;
return this;
}
/**
* Build the panel
* @return Panel
*/
public Panel build() {
// items.lastKey() is a slot position, so the panel size is this value + 1
return new Panel(name, items, Math.max(size, items.isEmpty() ? size : items.lastKey() + 1), user, listener, type);
return new Panel(this);
}
/**
@ -167,4 +178,14 @@ public class PanelBuilder {
public Panel.Type getPanelType() {
return type;
}
/**
* @return the world
* @since 1.16.0
*/
public World getWorld() {
return world;
}
}

View File

@ -1,6 +1,7 @@
package world.bentobox.bentobox.listeners.flags.clicklisteners;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.BentoBox;
@ -32,8 +33,9 @@ public class CommandCycleClick implements ClickHandler {
public boolean onClick(Panel panel, User user, ClickType click, int slot) {
// 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());
// Get the user's island for the game world
World world = panel.getWorld().orElse(user.getWorld());
Island island = plugin.getIslands().getIsland(world, user.getUniqueId());
if (island != null && island.getOwner().equals(user.getUniqueId())) {
RanksManager rm = plugin.getRanksManager();
int currentRank = island.getRankCommand(command);
@ -53,7 +55,7 @@ public class CommandCycleClick implements ClickHandler {
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
}
// Apply change to panel
panel.getInventory().setItem(slot, commandRankClickListener.getPanelItem(command, user).getItem());
panel.getInventory().setItem(slot, commandRankClickListener.getPanelItem(command, user, world).getItem());
// Save island
plugin.getIslands().save(island);

View File

@ -43,7 +43,7 @@ public class CommandRankClickListener implements ClickHandler {
}
// Check if has permission
String prefix = plugin.getIWM().getPermissionPrefix(Util.getWorld(user.getWorld()));
String prefix = plugin.getIWM().getPermissionPrefix(Util.getWorld(panel.getWorld().orElse(user.getWorld())));
String reqPerm = prefix + "settings." + Flags.COMMAND_RANKS.getID();
String allPerms = prefix + "settings.*";
if (!user.hasPermission(reqPerm) && !user.hasPermission(allPerms)
@ -54,7 +54,7 @@ public class CommandRankClickListener implements ClickHandler {
}
// Get the user's island
Island island = plugin.getIslands().getIsland(user.getWorld(), user.getUniqueId());
Island island = plugin.getIslands().getIsland(panel.getWorld().orElse(user.getWorld()), user.getUniqueId());
if (island == null || !island.getOwner().equals(user.getUniqueId())) {
user.sendMessage("general.errors.not-owner");
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
@ -65,24 +65,24 @@ public class CommandRankClickListener implements ClickHandler {
if (panel.getName().equals(panelName)) {
// This is a click on the panel
// Slot relates to the command
String c = getCommands(user.getWorld()).get(slot);
String c = getCommands(panel.getWorld().orElse(user.getWorld())).get(slot);
// Apply change to panel
panel.getInventory().setItem(slot, getPanelItem(c, user).getItem());
panel.getInventory().setItem(slot, getPanelItem(c, user, panel.getWorld().orElse(user.getWorld())).getItem());
} else {
// Open the Sub Settings panel
openPanel(user, panelName);
openPanel(user, panelName, panel.getWorld().orElse(user.getWorld()));
}
return true;
}
private void openPanel(User user, String panelName) {
private void openPanel(User user, String panelName, World world) {
// Close the current panel
user.closeInventory();
// Open a new panel
PanelBuilder pb = new PanelBuilder();
pb.user(user).name(panelName);
pb.user(user).name(panelName).world(world);
// Make panel items
getCommands(user.getWorld()).forEach(c -> pb.item(getPanelItem(c, user)));
getCommands(world).forEach(c -> pb.item(getPanelItem(c, user, world)));
pb.build();
}
@ -91,10 +91,11 @@ public class CommandRankClickListener implements ClickHandler {
* Gets the rank command panel item
* @param c - rank string
* @param user - user
* @param world - world for this panel
* @return panel item for this command
*/
public PanelItem getPanelItem(String c, User user) {
Island island = plugin.getIslands().getIsland(user.getWorld(), user);
public PanelItem getPanelItem(String c, User user, World world) {
Island island = plugin.getIslands().getIsland(world, user);
PanelItemBuilder pib = new PanelItemBuilder();
pib.name(c);
pib.clickHandler(new CommandCycleClick(this, c));

View File

@ -1,6 +1,7 @@
package world.bentobox.bentobox.listeners.flags.clicklisteners;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.BentoBox;
@ -26,8 +27,9 @@ public class GeoLimitClickListener implements ClickHandler {
user.sendMessage("general.errors.wrong-world");
return true;
}
World world = panel.getWorld().orElse(user.getWorld());
IslandWorldManager iwm = BentoBox.getInstance().getIWM();
String reqPerm = iwm.getPermissionPrefix(Util.getWorld(user.getWorld())) + "admin.settings.GEO_LIMIT_MOBS";
String reqPerm = iwm.getPermissionPrefix(Util.getWorld(world)) + "admin.settings.GEO_LIMIT_MOBS";
if (!user.hasPermission(reqPerm)) {
user.sendMessage("general.errors.no-permission", "[permission]", reqPerm);
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
@ -35,19 +37,19 @@ public class GeoLimitClickListener implements ClickHandler {
}
// Open the Sub Settings panel
openPanel(user);
openPanel(user, world);
return true;
}
private void openPanel(User user) {
private void openPanel(User user, World world) {
// Close the current panel
user.closeInventory();
// Open a new panel
new TabbedPanelBuilder()
.user(user)
.world(user.getWorld())
.tab(1, new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT))
.world(world)
.tab(1, new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT, world))
.startingSlot(1)
.size(54)
.build().openPanel();

View File

@ -7,11 +7,13 @@ import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.event.inventory.ClickType;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.panels.Panel;
@ -47,43 +49,45 @@ public class GeoMobLimitTab implements Tab, ClickHandler {
private final BentoBox plugin = BentoBox.getInstance();
private final User user;
private final EntityLimitTabType type;
private final World world;
/**
* @param user - user viewing the tab
* @param type - type of tab to show - Geo limit or Mob limit
* @param world - world where this tab is being used
*/
public GeoMobLimitTab(@NonNull User user, @NonNull EntityLimitTabType type) {
public GeoMobLimitTab(@NonNull User user, @NonNull EntityLimitTabType type, World world) {
super();
this.user = user;
this.type = type;
this.world = world;
}
@Override
public boolean onClick(Panel panel, User user, ClickType clickType, int slot) {
// This is a click on the mob limit panel
// Case panel to Tabbed Panel to get the active page
TabbedPanel tp = (TabbedPanel)panel;
// Convert the slot and active page to an index
int index = tp.getActivePage() * 36 + slot - 9;
EntityType c = LIVING_ENTITY_TYPES.get(index);
if (type == EntityLimitTabType.MOB_LIMIT) {
if (plugin.getIWM().getMobLimitSettings(user.getWorld()).contains(c.name())) {
plugin.getIWM().getMobLimitSettings(user.getWorld()).remove(c.name());
if (plugin.getIWM().getMobLimitSettings(world).contains(c.name())) {
plugin.getIWM().getMobLimitSettings(world).remove(c.name());
} else {
plugin.getIWM().getMobLimitSettings(user.getWorld()).add(c.name());
plugin.getIWM().getMobLimitSettings(world).add(c.name());
}
} else {
if (plugin.getIWM().getGeoLimitSettings(user.getWorld()).contains(c.name())) {
plugin.getIWM().getGeoLimitSettings(user.getWorld()).remove(c.name());
if (plugin.getIWM().getGeoLimitSettings(world).contains(c.name())) {
plugin.getIWM().getGeoLimitSettings(world).remove(c.name());
} else {
plugin.getIWM().getGeoLimitSettings(user.getWorld()).add(c.name());
plugin.getIWM().getGeoLimitSettings(world).add(c.name());
}
}
// Apply change to panel
panel.getInventory().setItem(slot, getPanelItem(c, user).getItem());
// Save settings
plugin.getIWM().getAddon(Util.getWorld(user.getWorld())).ifPresent(GameModeAddon::saveWorldSettings);
plugin.getIWM().getAddon(Util.getWorld(world)).ifPresent(GameModeAddon::saveWorldSettings);
return true;
}
@ -119,7 +123,7 @@ public class GeoMobLimitTab implements Tab, ClickHandler {
pib.name(Util.prettifyText(c.toString()));
pib.clickHandler(this);
if (type == EntityLimitTabType.MOB_LIMIT) {
if (!BentoBox.getInstance().getIWM().getMobLimitSettings(user.getWorld()).contains(c.name())) {
if (!BentoBox.getInstance().getIWM().getMobLimitSettings(world).contains(c.name())) {
pib.icon(Material.GREEN_SHULKER_BOX);
pib.description(user.getTranslation("protection.flags.LIMIT_MOBS.can"));
} else {
@ -127,7 +131,7 @@ public class GeoMobLimitTab implements Tab, ClickHandler {
pib.description(user.getTranslation("protection.flags.LIMIT_MOBS.cannot"));
}
} else {
if (BentoBox.getInstance().getIWM().getGeoLimitSettings(user.getWorld()).contains(c.name())) {
if (BentoBox.getInstance().getIWM().getGeoLimitSettings(world).contains(c.name())) {
pib.icon(Material.GREEN_SHULKER_BOX);
pib.description(user.getTranslation("protection.panel.flag-item.setting-active"));
} else {

View File

@ -1,6 +1,7 @@
package world.bentobox.bentobox.listeners.flags.clicklisteners;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.BentoBox;
@ -26,8 +27,9 @@ public class MobLimitClickListener implements ClickHandler {
user.sendMessage("general.errors.wrong-world");
return true;
}
World world = panel.getWorld().orElse(user.getWorld());
IslandWorldManager iwm = BentoBox.getInstance().getIWM();
String reqPerm = iwm.getPermissionPrefix(Util.getWorld(user.getWorld())) + "admin.settings.LIMIT_MOBS";
String reqPerm = iwm.getPermissionPrefix(Util.getWorld(world)) + "admin.settings.LIMIT_MOBS";
if (!user.hasPermission(reqPerm)) {
user.sendMessage("general.errors.no-permission", "[permission]", reqPerm);
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
@ -35,19 +37,19 @@ public class MobLimitClickListener implements ClickHandler {
}
// Open the Sub Settings panel
openPanel(user);
openPanel(user, world);
return true;
}
private void openPanel(User user) {
private void openPanel(User user, World world) {
// Close the current panel
user.closeInventory();
// Open a new panel
new TabbedPanelBuilder()
.user(user)
.world(user.getWorld())
.tab(1, new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT))
.world(world)
.tab(1, new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT, world))
.startingSlot(1)
.size(54)
.build().openPanel();

View File

@ -47,16 +47,15 @@ public class SettingsTab implements Tab, ClickHandler {
/**
* Show a tab of settings
* @param world - world
* @param user - user who is viewing the tab
* @param island - the island
* @param type - flag type
*/
public SettingsTab(World world, User user, Island island, Type type) {
this.world = world;
public SettingsTab(User user, Island island, Type type) {
this.user = user;
this.island = island;
this.type = type;
this.world = island.getWorld();
}
/**

View File

@ -2,8 +2,8 @@ package world.bentobox.bentobox.util.heads;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -14,6 +14,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.Gson;
@ -114,48 +115,96 @@ public class HeadGetter {
/**
* This is main task that runs once every 20 ticks and tries to get a player head.
* This is main task that runs once every Settings#ticksBetweenCalls ticks and tries to get
* Settings#headsPerCall player heads at once.
*
* @since 1.14.1
*/
private void runPlayerHeadGetter() {
Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
synchronized (HeadGetter.names)
{
if (!HeadGetter.names.isEmpty())
int counter = 0;
while (!HeadGetter.names.isEmpty() && counter < plugin.getSettings().getHeadsPerCall())
{
Pair<String, PanelItem> elementEntry = HeadGetter.names.poll();
// TODO: In theory BentoBox could use User instance to find existing user UUID's.
// It would avoid one API call.
final String userName = elementEntry.getKey();
// Use cached userId as userId will not change :)
UUID userId = HeadGetter.cachedHeads.containsKey(userName) ?
HeadGetter.cachedHeads.get(userName).getUserId() :
HeadGetter.getUserIdFromName(userName);
// Hmm, task in task in task. That is a weird structure.
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Check if we can get user Id.
UUID userId;
// Create new cache object.
HeadCache cache = new HeadCache(userName,
userId,
HeadGetter.getTextureFromUUID(userId));
// Save in cache
HeadGetter.cachedHeads.put(userName, cache);
// Tell requesters the head came in
if (HeadGetter.headRequesters.containsKey(userName))
{
for (HeadRequester req : HeadGetter.headRequesters.get(userName))
if (HeadGetter.cachedHeads.containsKey(userName))
{
elementEntry.getValue().setHead(cache.getPlayerHead());
Bukkit.getServer().getScheduler().runTaskAsynchronously(this.plugin,
() -> req.setHead(elementEntry.getValue()));
// If cache contains userName, it means that it was already stored.
// We can reuse stored data, as they should not be changed.
userId = HeadGetter.cachedHeads.get(userName).getUserId();
}
}
else if (Bukkit.getServer().getOnlineMode())
{
// If server is in online mode we can relay that UUID is correct.
// So we use thing that is stored in BentoBox players data.
userId = plugin.getPlayers().getUUID(userName);
}
else
{
// Assign null for later check, as I do not want to write ifs inside
// previous 2 checks.
userId = null;
}
HeadCache cache;
if (plugin.getSettings().isUseCacheServer())
{
// Cache server has an implementation to get a skin just from player name.
Pair<UUID, String> playerSkin = HeadGetter.getTextureFromName(userName, userId);
// Create new cache object.
cache = new HeadCache(userName,
playerSkin.getKey(),
playerSkin.getValue());
}
else
{
if (userId == null)
{
// Use MojangAPI to get userId from userName.
userId = HeadGetter.getUserIdFromName(userName);
}
// Create new cache object.
cache = new HeadCache(userName,
userId,
HeadGetter.getTextureFromUUID(userId));
}
// Save in cache
HeadGetter.cachedHeads.put(userName, cache);
// Tell requesters the head came in, but only if the texture is usable.
if (cache.encodedTextureLink != null && HeadGetter.headRequesters.containsKey(userName))
{
for (HeadRequester req : HeadGetter.headRequesters.get(userName))
{
elementEntry.getValue().setHead(cache.getPlayerHead());
if (!plugin.isShutdown())
{
// Do not run task if plugin is shutting down.
Bukkit.getScheduler().runTaskAsynchronously(this.plugin,
() -> req.setHead(elementEntry.getValue()));
}
}
}
});
counter++;
}
}
}, 0L, 10L);
}, 0, plugin.getSettings().getTicksBetweenCalls());
}
@ -189,8 +238,7 @@ public class HeadGetter {
// UUID just looks more fancy :)
String userIdString = jsonObject.get("id").toString().
replace("\"", "").
replaceFirst("([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)",
"$1-$2-$3-$4-$5");
replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5");
userId = UUID.fromString(userIdString);
}
@ -260,6 +308,70 @@ public class HeadGetter {
}
/**
* This method gets and returns base64 encoded link to player skin texture from mc-heads.net.
* It tries to use UUID if it is a valid, otherwise it uses given username.
*
* @param userName userName
* @param userId UUID for the user.
* @return Encoded player skin texture or null.
* @since 1.16.0
*/
private static @NonNull Pair<UUID, String> getTextureFromName(String userName, @Nullable UUID userId) {
try
{
Gson gsonReader = new Gson();
// Get user encoded texture value.
// mc-heads returns correct skin with providing just a name, unlike mojang api, which
// requires UUID.
JsonObject jsonObject = gsonReader.fromJson(
HeadGetter.getURLContent("https://mc-heads.net/minecraft/profile/" + (userId == null ? userName : userId.toString())),
JsonObject.class);
/*
* Returned Json Object:
{
id: USER_ID,
name: USER_NAME,
properties: [
{
name: "textures",
value: ENCODED_BASE64_TEXTURE
}
]
}
*/
String decodedTexture = "";
String userIdString = jsonObject.get("id").toString().
replace("\"", "").
replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5");
for (JsonElement element : jsonObject.getAsJsonArray("properties"))
{
JsonObject object = element.getAsJsonObject();
if (object.has("name") &&
object.get("name").getAsString().equals("textures"))
{
decodedTexture = object.get("value").getAsString();
break;
}
}
return new Pair<>(UUID.fromString(userIdString), decodedTexture);
}
catch (Exception ignored)
{
}
// return random uuid and null, to assign some values for cache.
return new Pair<>(userId, null);
}
/**
* This method gets page content of requested url
*
@ -270,12 +382,15 @@ public class HeadGetter {
private static String getURLContent(String requestedUrl) {
String returnValue;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new URL(requestedUrl).openStream(), StandardCharsets.UTF_8)))
try
{
returnValue = reader.lines().collect(Collectors.joining());
URL url = new URL(requestedUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
returnValue = br.lines().collect(Collectors.joining());
br.close();
}
catch (Exception ignored)
catch (Exception e)
{
returnValue = "";
}

View File

@ -82,11 +82,25 @@ panel:
# Defines the Material of the item that fills the gaps (in the header, etc.) of most panels.
# Added since 1.14.0.
filler-material: LIGHT_BLUE_STAINED_GLASS_PANE
# Toggle whether player head texture should be gathered from Mojang API or mc-heads.net cache server.
# Mojang API sometime may be slow and may limit requests to the player data, so this will allow to
# get player heads a bit faster then Mojang API.
# Added since 1.16.0.
use-cache-server: true
# Defines how long player skin texture link is stored into local cache before it is requested again.
# Defined value is in the minutes.
# Value 0 will not clear cache until server restart.
# Added since 1.14.1.
head-cache-time: 60
# Defines a number of player heads requested per tasks.
# Setting it too large may lead to temporarily being blocked from head gatherer API.
# Added since 1.16.0.
heads-per-call: 9
# Defines a number of ticks between each player head request task.
# Setting it too large may lead to temporarily being blocked from head gatherer API.
# Added since 1.16.0.
# /!\ In order to apply the changes made to this option, you must restart your server. Reloading BentoBox or the server won't work.
ticks-between-calls: 10
logs:
# Toggle whether superflat chunks regeneration should be logged in the server logs or not.
# It can be spammy if there are a lot of superflat chunks to regenerate.

View File

@ -15,6 +15,7 @@ import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.Inventory;
import org.eclipse.jdt.annotation.NonNull;
@ -38,18 +39,21 @@ import world.bentobox.bentobox.api.panels.TabbedPanel;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.listeners.flags.clicklisteners.GeoMobLimitTab.EntityLimitTabType;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, BentoBox.class})
@PrepareForTest({Bukkit.class, BentoBox.class, Util.class})
public class GeoMobLimitTabTest {
@Mock
private User user;
@Mock
private World world;
@Mock
private TabbedPanel panel;
@Mock
private BentoBox plugin;
@ -82,6 +86,9 @@ public class GeoMobLimitTabTest {
when(panel.getInventory()).thenReturn(inv);
// User
when(user.getTranslation(anyString())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
// Util
PowerMockito.mockStatic(Util.class, Mockito.CALLS_REAL_METHODS);
when(Util.getWorld(any())).thenReturn(world);
}
@After
@ -94,7 +101,7 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testOnClick() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT, world);
// BAT and COW in list
assertEquals(2, list.size());
assertEquals("COW", list.get(1));
@ -116,7 +123,7 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testOnClickMobLimit() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT, world);
// BAT and COW in list
assertEquals(2, list.size());
assertEquals("COW", list.get(1));
@ -138,7 +145,7 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testGetIcon() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT, world);
PanelItem icon = tab.getIcon();
assertEquals("protection.flags.LIMIT_MOBS.name", icon.getName());
assertEquals(Material.IRON_BOOTS, icon.getItem().getType());
@ -149,7 +156,7 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testGetIconGeoLimit() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT, world);
PanelItem icon = tab.getIcon();
assertEquals("protection.flags.GEO_LIMIT_MOBS.name", icon.getName());
assertEquals(Material.CHAINMAIL_CHESTPLATE, icon.getItem().getType());
@ -160,9 +167,9 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testGetName() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT, world);
assertEquals("protection.flags.LIMIT_MOBS.name", tab.getName());
tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT);
tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT, world);
assertEquals("protection.flags.GEO_LIMIT_MOBS.name", tab.getName());
}
@ -171,14 +178,14 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testGetPanelItemsMobLimit() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.MOB_LIMIT, world);
List<@Nullable PanelItem> items = tab.getPanelItems();
assertFalse(items.isEmpty());
items.forEach(i -> {
if (i.getName().equals("Cow") || i.getName().equals("Bat")) {
assertEquals(Material.RED_SHULKER_BOX, i.getItem().getType());
assertEquals("Name : " + i.getName(), Material.RED_SHULKER_BOX, i.getItem().getType());
} else {
assertEquals(Material.GREEN_SHULKER_BOX, i.getItem().getType());
assertEquals("Name : " + i.getName(), Material.GREEN_SHULKER_BOX, i.getItem().getType());
}
});
}
@ -188,14 +195,14 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testGetPanelItemsGeoLimit() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT, world);
List<@Nullable PanelItem> items = tab.getPanelItems();
assertFalse(items.isEmpty());
items.forEach(i -> {
if (i.getName().equals("Cow") || i.getName().equals("Bat")) {
assertEquals(Material.GREEN_SHULKER_BOX, i.getItem().getType());
assertEquals("Name : " + i.getName(), Material.GREEN_SHULKER_BOX, i.getItem().getType());
} else {
assertEquals(Material.RED_SHULKER_BOX, i.getItem().getType());
assertEquals("Name : " + i.getName(), Material.RED_SHULKER_BOX, i.getItem().getType());
}
});
}
@ -205,7 +212,7 @@ public class GeoMobLimitTabTest {
*/
@Test
public void testGetPermission() {
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT);
GeoMobLimitTab tab = new GeoMobLimitTab(user, EntityLimitTabType.GEO_LIMIT, world);
assertTrue(tab.getPermission().isEmpty());
}