Added EnderChest protection.

Enderchests are an explout because they allow transfer of items between
worlds. These additions enable usage to be switched on/off at the world
level. Also prevents ender chest crafting. These protections can be
bypassed via a permission or via Op.

Also, I fixed some settings issues in general around default settings.
This commit is contained in:
tastybento 2018-06-17 21:37:50 -07:00
parent fba05ecc81
commit 108d099156
10 changed files with 364 additions and 23 deletions

View File

@ -331,6 +331,10 @@ protection:
ENCHANTING:
description: "Toggle use"
name: "Enchanting table"
ENDER_CHEST:
description: "Toggle use/crafting"
name: "Ender Chests"
hint: "Ender chests are disabled in this world"
ENDER_PEARL:
description: "Toggle use"
name: "EnderPearls"

View File

@ -229,16 +229,12 @@ public class Settings implements DataObject, WorldSettings {
@ConfigComment("Disable redstone operation on islands unless a team member is online.")
@ConfigComment("This may reduce lag but it can cause problems with visitors needing to use a redstone system.")
@ConfigComment("Default is false, because it is an experimental feature that can break a lot of redstone systems.")
@ConfigEntry(path = "world.disable-offline-redstone")
private boolean disableOfflineRedstone = false;
@ConfigComment("World flags. These are boolean settings for various flags for this world")
@ConfigEntry(path = "world.flags")
private Map<String, Boolean> worldFlags = new HashMap<>();
{
worldFlags.put("ENTER_EXIT_MESSAGES", true);
worldFlags.put("PISTON_PUSH", true);
worldFlags.put("REMOVE_MOBS", true);
}
// ---------------------------------------------

View File

@ -73,8 +73,17 @@ public class Flag implements Comparable<Flag> {
* If world is not a game world, then the result will always be false!
*/
public boolean isSetForWorld(World world) {
WorldSettings ws = BSkyBlock.getInstance().getIWM().getWorldSettings(world);
return ws != null ? ws.getWorldFlags().getOrDefault(getID(), setting) : false;
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;
}
}
/**

View File

@ -1,16 +1,11 @@
package us.tastybento.bskyblock.api.localization;
import java.io.File;
import java.util.List;
import java.util.Locale;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.block.banner.Pattern;
import org.bukkit.block.banner.PatternType;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BannerMeta;
import us.tastybento.bskyblock.util.ItemParser;
/**
@ -22,7 +17,6 @@ public class BSBLocale {
private YamlConfiguration config;
private ItemStack banner;
@SuppressWarnings("deprecation")
public BSBLocale(Locale locale, File file) {
this.locale = locale;
config = YamlConfiguration.loadConfiguration(file);

View File

@ -94,7 +94,8 @@ public class FlatFileDatabaseConnecter implements DatabaseConnecter {
try {
// Approach is save to temp file (saving is not necessarily atomic), then move file atomically
// This has best chance of no file corruption
File tmpFile = File.createTempFile(tableName, ".tmp", tableFolder);
File tmpFile = File.createTempFile("yaml", null, tableFolder);
yamlConfig.save(tmpFile);
if (tmpFile.exists()) {
Files.move(tmpFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
@ -102,7 +103,7 @@ public class FlatFileDatabaseConnecter implements DatabaseConnecter {
throw new Exception();
}
} catch (Exception e) {
plugin.logError("Could not save yaml file to database " + tableName + " " + fileName + " " + e.getMessage());
plugin.logError("Could not save yaml file: " + tableName + " " + fileName + " " + e.getMessage());
return;
}
if (commentMap != null && !commentMap.isEmpty()) {

View File

@ -0,0 +1,59 @@
/**
*
*/
package us.tastybento.bskyblock.listeners.flags;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import us.tastybento.bskyblock.api.localization.TextVariables;
import us.tastybento.bskyblock.api.user.User;
import us.tastybento.bskyblock.lists.Flags;
/**
* Prevents enderchest use and creation in world if it is not allowed
* @author tastybento
*
*/
public class EnderChestListener extends AbstractFlagListener {
/**
* Prevents opening ender chest unless player has permission
* @param e
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onEnderChestOpen(PlayerInteractEvent e) {
if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) {
e.setCancelled(checkEnderChest(e.getPlayer(), e.getClickedBlock().getType()));
}
}
/**
* Prevents crafting of EnderChest unless the player has permission
*
* @param event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onCraft(CraftItemEvent e) {
e.setCancelled(checkEnderChest((Player)e.getWhoClicked(), e.getRecipe().getResult().getType()));
}
private boolean checkEnderChest(Player player, Material type) {
if (type.equals(Material.ENDER_CHEST)
&& getIslandWorldManager().inWorld(player.getLocation())
&& !player.isOp()
&& !player.hasPermission(getPlugin().getIWM().getPermissionPrefix(player.getWorld()) + ".craft.enderchest")
&& !Flags.ENDER_CHEST.isSetForWorld(player.getWorld())) {
// Not allowed
User user = User.getInstance(player);
user.sendMessage("protection.protected", TextVariables.DESCRIPTION, user.getTranslation(Flags.ENDER_CHEST.getHintReference()));
return true;
}
return false;
}
}

View File

@ -7,7 +7,6 @@ import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import us.tastybento.bskyblock.api.flags.Flag;
import us.tastybento.bskyblock.api.flags.Flag.Type;
import us.tastybento.bskyblock.api.flags.FlagBuilder;
@ -16,6 +15,7 @@ import us.tastybento.bskyblock.listeners.flags.BreakBlocksListener;
import us.tastybento.bskyblock.listeners.flags.BreedingListener;
import us.tastybento.bskyblock.listeners.flags.BucketListener;
import us.tastybento.bskyblock.listeners.flags.EggListener;
import us.tastybento.bskyblock.listeners.flags.EnderChestListener;
import us.tastybento.bskyblock.listeners.flags.EnterExitListener;
import us.tastybento.bskyblock.listeners.flags.EntityInteractListener;
import us.tastybento.bskyblock.listeners.flags.FireListener;
@ -54,7 +54,6 @@ public class Flags {
public static final Flag BED = new FlagBuilder().id("BED").icon(Material.BED).build();
public static final Flag BREWING = new FlagBuilder().id("BREWING").icon(Material.BREWING_STAND_ITEM).build();
public static final Flag CHEST = new FlagBuilder().id("CHEST").icon(Material.CHEST).build();
public static final Flag ENDER_CHEST = new FlagBuilder().id("ENDER_CHEST").icon(Material.ENDER_CHEST).build();
public static final Flag DOOR = new FlagBuilder().id("DOOR").allowedByDefault(true).icon(Material.WOOD_DOOR).build();
public static final Flag TRAPDOOR = new FlagBuilder().id("TRAPDOOR").allowedByDefault(true).icon(Material.TRAP_DOOR).build();
public static final Flag CRAFTING = new FlagBuilder().id("CRAFTING").allowedByDefault(true).icon(Material.WORKBENCH).build();
@ -149,7 +148,12 @@ public class Flags {
public static final Flag MONSTER_SPAWN = new FlagBuilder().id("MONSTER_SPAWN").icon(Material.MOB_SPAWNER).allowedByDefault(true).type(Type.SETTING).build();
public static final Flag FIRE_SPREAD = new FlagBuilder().id("FIRE_SPREAD").icon(Material.FIREWORK_CHARGE).type(Type.SETTING).build();
// Global flags (apply to every island)
// World Settings - apply to every island in the game worlds
public static final Flag ENDER_CHEST = new FlagBuilder().id("ENDER_CHEST").icon(Material.ENDER_CHEST)
.allowedByDefault(false).type(Type.WORLD_SETTING)
.listener(new EnderChestListener())
.onClick(new WorldToggleClickListener("ENDER_CHEST"))
.build();
public static final Flag ENTER_EXIT_MESSAGES = new FlagBuilder().id("ENTER_EXIT_MESSAGES").icon(Material.DIRT).allowedByDefault(true).type(Type.WORLD_SETTING)
.listener(new EnterExitListener())
.onClick(new WorldToggleClickListener("ENTER_EXIT_MESSAGES"))

View File

@ -468,12 +468,13 @@ public class IslandWorldManager {
/**
* Returns whether a world flag is set or not
* Same result as calling {@link Flag#isSetForWorld(World)}
* @param world - world
* @param flag - world setting flag
* @return true or false
*/
public boolean isWorldFlag(World world, Flag flag) {
return worldSettings.get(Util.getWorld(world)).getWorldFlags().get(flag.getID());
return flag.isSetForWorld(world);
}
}

View File

@ -35,6 +35,7 @@ import org.powermock.reflect.Whitebox;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.api.configuration.WorldSettings;
import us.tastybento.bskyblock.api.flags.Flag.Type;
import us.tastybento.bskyblock.api.panels.PanelItem;
import us.tastybento.bskyblock.api.user.User;
import us.tastybento.bskyblock.database.objects.Island;
@ -112,15 +113,17 @@ public class FlagTest {
@Test
public void testIsDefaultSetting() {
Flag id = new Flag("id", Material.ACACIA_DOOR, null, false, null, 0, null, false);
Type type = Type.SETTING;
Flag id = new Flag("id", Material.ACACIA_DOOR, null, false, type , 0, null, false);
assertFalse(id.isSetForWorld(mock(World.class)));
id = new Flag("id", Material.ACACIA_DOOR, null, true, null, 0, null, false);
id = new Flag("id", Material.ACACIA_DOOR, null, true, type, 0, null, false);
assertTrue(id.isSetForWorld(mock(World.class)));
}
@Test
public void testSetDefaultSetting() {
Flag id = new Flag("id", Material.ACACIA_DOOR, null, false, null, 0, null, false);
Type type = Type.SETTING;
Flag id = new Flag("id", Material.ACACIA_DOOR, null, false, type, 0, null, false);
assertFalse(id.isSetForWorld(mock(World.class)));
id.setDefaultSetting(true);
assertTrue(id.isSetForWorld(mock(World.class)));
@ -128,6 +131,16 @@ public class FlagTest {
assertFalse(id.isSetForWorld(mock(World.class)));
}
@Test
public void testIsDefaultSetting_World_Setting() {
Type type = Type.WORLD_SETTING;
Flag id = new Flag("id", Material.ACACIA_DOOR, null, false, type , 0, null, false);
assertFalse(id.isSetForWorld(mock(World.class)));
// Default can only be set once with world settings, so use a new id for flag
id = new Flag("id2", Material.ACACIA_DOOR, null, true, type, 0, null, false);
assertTrue(id.isSetForWorld(mock(World.class)));
}
@Test
public void testGetType() {

View File

@ -0,0 +1,260 @@
package us.tastybento.bskyblock.listeners.flags;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryType.SlotType;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.api.configuration.WorldSettings;
import us.tastybento.bskyblock.api.user.User;
import us.tastybento.bskyblock.database.objects.Island;
import us.tastybento.bskyblock.lists.Flags;
import us.tastybento.bskyblock.managers.IslandWorldManager;
import us.tastybento.bskyblock.managers.IslandsManager;
import us.tastybento.bskyblock.managers.LocalesManager;
import us.tastybento.bskyblock.util.Util;
@RunWith(PowerMockRunner.class)
@PrepareForTest({BSkyBlock.class, Util.class, User.class })
public class EnderChestListenerTest {
private Island island;
private IslandsManager im;
private World world;
private Location inside;
private UUID uuid;
private Player player;
private IslandWorldManager iwm;
@Before
public void setUp() throws Exception {
// Set up plugin
BSkyBlock plugin = mock(BSkyBlock.class);
Whitebox.setInternalState(BSkyBlock.class, "instance", plugin);
// World
world = mock(World.class);
// Owner
uuid = UUID.randomUUID();
// Island initialization
island = mock(Island.class);
when(island.getOwner()).thenReturn(uuid);
im = mock(IslandsManager.class);
when(plugin.getIslands()).thenReturn(im);
when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island);
inside = mock(Location.class);
when(inside.getWorld()).thenReturn(world);
Optional<Island> opIsland = Optional.ofNullable(island);
when(im.getProtectedIslandAt(Mockito.eq(inside))).thenReturn(opIsland);
// On island
when(im.locationIsOnIsland(Mockito.any(), Mockito.any())).thenReturn(true);
PowerMockito.mockStatic(Util.class);
when(Util.getWorld(Mockito.any())).thenReturn(world);
// World Settings
iwm = mock(IslandWorldManager.class);
when(plugin.getIWM()).thenReturn(iwm);
WorldSettings ws = mock(WorldSettings.class);
when(iwm.getWorldSettings(Mockito.any())).thenReturn(ws);
Map<String, Boolean> worldFlags = new HashMap<>();
when(ws.getWorldFlags()).thenReturn(worldFlags);
// By default everything is in world
when(iwm.inWorld(Mockito.any())).thenReturn(true);
// Ender chest use is not allowed by default
Flags.ENDER_CHEST.setSetting(world, false);
// Sometimes use Mockito.withSettings().verboseLogging()
player = mock(Player.class);
UUID uuid = UUID.randomUUID();
when(player.getUniqueId()).thenReturn(uuid);
when(player.isOp()).thenReturn(false);
// No special perms
when(player.hasPermission(Mockito.anyString())).thenReturn(false);
when(player.getWorld()).thenReturn(world);
// Locales - this returns the string that was requested for translation
LocalesManager lm = mock(LocalesManager.class);
when(plugin.getLocalesManager()).thenReturn(lm);
when(lm.get(any(), any())).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArgumentAt(1, String.class);
}});
}
@Test
public void testOnEnderChestOpenNotRightClick() {
Action action = Action.LEFT_CLICK_AIR;
ItemStack item = mock(ItemStack.class);
Block clickedBlock = mock(Block.class);
BlockFace clickedBlockFace = BlockFace.EAST;
PlayerInteractEvent e = new PlayerInteractEvent(player, action, item, clickedBlock, clickedBlockFace);
new EnderChestListener().onEnderChestOpen(e);
assertFalse(e.isCancelled());
}
@Test
public void testOnEnderChestOpenNotEnderChest() {
Action action = Action.RIGHT_CLICK_BLOCK;
ItemStack item = mock(ItemStack.class);
Block clickedBlock = mock(Block.class);
when(clickedBlock.getType()).thenReturn(Material.STONE);
BlockFace clickedBlockFace = BlockFace.EAST;
PlayerInteractEvent e = new PlayerInteractEvent(player, action, item, clickedBlock, clickedBlockFace);
new EnderChestListener().onEnderChestOpen(e);
assertFalse(e.isCancelled());
}
@Test
public void testOnEnderChestOpenEnderChestNotInWorld() {
Action action = Action.RIGHT_CLICK_BLOCK;
ItemStack item = mock(ItemStack.class);
Block clickedBlock = mock(Block.class);
when(clickedBlock.getType()).thenReturn(Material.ENDER_CHEST);
BlockFace clickedBlockFace = BlockFace.EAST;
PlayerInteractEvent e = new PlayerInteractEvent(player, action, item, clickedBlock, clickedBlockFace);
// Not in world
when(iwm.inWorld(Mockito.any())).thenReturn(false);
new EnderChestListener().onEnderChestOpen(e);
assertFalse(e.isCancelled());
}
@Test
public void testOnEnderChestOpenEnderChestOpPlayer() {
Action action = Action.RIGHT_CLICK_BLOCK;
ItemStack item = mock(ItemStack.class);
Block clickedBlock = mock(Block.class);
when(clickedBlock.getType()).thenReturn(Material.ENDER_CHEST);
BlockFace clickedBlockFace = BlockFace.EAST;
PlayerInteractEvent e = new PlayerInteractEvent(player, action, item, clickedBlock, clickedBlockFace);
// Op player
when(player.isOp()).thenReturn(true);
new EnderChestListener().onEnderChestOpen(e);
assertFalse(e.isCancelled());
}
@Test
public void testOnEnderChestOpenEnderChestHasBypassPerm() {
Action action = Action.RIGHT_CLICK_BLOCK;
ItemStack item = mock(ItemStack.class);
Block clickedBlock = mock(Block.class);
when(clickedBlock.getType()).thenReturn(Material.ENDER_CHEST);
BlockFace clickedBlockFace = BlockFace.EAST;
PlayerInteractEvent e = new PlayerInteractEvent(player, action, item, clickedBlock, clickedBlockFace);
// Has bypass perm
when(player.hasPermission(Mockito.anyString())).thenReturn(true);
new EnderChestListener().onEnderChestOpen(e);
assertFalse(e.isCancelled());
}
@Test
public void testOnEnderChestOpenEnderChestOkay() {
Action action = Action.RIGHT_CLICK_BLOCK;
ItemStack item = mock(ItemStack.class);
Block clickedBlock = mock(Block.class);
when(clickedBlock.getType()).thenReturn(Material.ENDER_CHEST);
BlockFace clickedBlockFace = BlockFace.EAST;
PlayerInteractEvent e = new PlayerInteractEvent(player, action, item, clickedBlock, clickedBlockFace);
// Enderchest use is okay
Flags.ENDER_CHEST.setSetting(world, true);
new EnderChestListener().onEnderChestOpen(e);
assertFalse(e.isCancelled());
}
@Test
public void testOnEnderChestOpenEnderChestBlocked() {
Action action = Action.RIGHT_CLICK_BLOCK;
ItemStack item = mock(ItemStack.class);
Block clickedBlock = mock(Block.class);
when(clickedBlock.getType()).thenReturn(Material.ENDER_CHEST);
BlockFace clickedBlockFace = BlockFace.EAST;
PlayerInteractEvent e = new PlayerInteractEvent(player, action, item, clickedBlock, clickedBlockFace);
// Enderchest use is okay
Flags.ENDER_CHEST.setSetting(world, false);
new EnderChestListener().onEnderChestOpen(e);
assertTrue(e.isCancelled());
Mockito.verify(player).sendMessage("protection.protected");
}
@Test
public void testOnCraftNotEnderChest() {
Recipe recipe = mock(Recipe.class);
ItemStack item = mock(ItemStack.class);
when(item.getType()).thenReturn(Material.STONE);
when(recipe.getResult()).thenReturn(item);
InventoryView view = mock(InventoryView.class);
when(view.getPlayer()).thenReturn(player);
Inventory top = mock(Inventory.class);
when(top.getSize()).thenReturn(9);
when(view.getTopInventory()).thenReturn(top);
SlotType type = SlotType.RESULT;
ClickType click = ClickType.LEFT;
InventoryAction action = InventoryAction.PICKUP_ONE;
CraftItemEvent e = new CraftItemEvent(recipe, view, type, 0, click, action);
new EnderChestListener().onCraft(e);
assertFalse(e.isCancelled());
}
@Test
public void testOnCraftEnderChest() {
Recipe recipe = mock(Recipe.class);
ItemStack item = mock(ItemStack.class);
when(item.getType()).thenReturn(Material.ENDER_CHEST);
when(recipe.getResult()).thenReturn(item);
InventoryView view = mock(InventoryView.class);
when(view.getPlayer()).thenReturn(player);
Inventory top = mock(Inventory.class);
when(top.getSize()).thenReturn(9);
when(view.getTopInventory()).thenReturn(top);
SlotType type = SlotType.RESULT;
ClickType click = ClickType.LEFT;
InventoryAction action = InventoryAction.PICKUP_ONE;
CraftItemEvent e = new CraftItemEvent(recipe, view, type, 0, click, action);
new EnderChestListener().onCraft(e);
assertTrue(e.isCancelled());
}
}