From 2c7ae708ec645b427952e70257ed6065bcbed522 Mon Sep 17 00:00:00 2001 From: Daniel Saukel Date: Sun, 10 Oct 2021 16:23:02 +0200 Subject: [PATCH] Rewrite break / place game rules; resolves #1041 --- .../dungeonsxl/api/dungeon/BuildMode.java | 71 +++++++++++++++++ .../dungeonsxl/api/dungeon/ConfigReader.java | 27 +++++++ .../dungeonsxl/api/dungeon/GameRule.java | 22 ++---- .../dungeonsxl/api/world/GameWorld.java | 8 ++ .../erethon/dungeonsxl/world/DGameWorld.java | 76 ++++++++----------- 5 files changed, 143 insertions(+), 61 deletions(-) create mode 100644 api/src/main/java/de/erethon/dungeonsxl/api/dungeon/BuildMode.java diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/BuildMode.java b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/BuildMode.java new file mode 100644 index 00000000..d8477a5d --- /dev/null +++ b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/BuildMode.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2014-2021 Daniel Saukel + * + * This library is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNULesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package de.erethon.dungeonsxl.api.dungeon; + +import de.erethon.dungeonsxl.api.world.GameWorld; +import java.util.HashMap; +import java.util.Map; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +/** + * Checks for whether a block may be broken. + * + * @author Daniel Saukel + */ +public interface BuildMode { + + /** + * Stores the pre-set breaking rules. + */ + static class Registry { + /** + * Entry keys must be lowercase. + */ + public static final Map ENTRIES = new HashMap<>(); + + static { + ENTRIES.put("true", TRUE); + ENTRIES.put("false", FALSE); + ENTRIES.put("placed", PLACED); + } + } + + /** + * All blocks except for protected ones may be broken. + */ + static final BuildMode TRUE = (Player player, GameWorld gameWorld, Block block) -> true; + /** + * Blocks may not be broken. + */ + static final BuildMode FALSE = (Player player, GameWorld gameWorld, Block block) -> false; + /** + * Blocks placed by players may be broken. + */ + static final BuildMode PLACED = (Player player, GameWorld gameWorld, Block block) -> gameWorld.getPlacedBlocks().contains(block); + + /** + * Returns if the block can be broken or placed by the player. + *

+ * The plugin protects dungeon signs before checking this. + * + * @param player the player who breaks or places the block + * @param gameWorld the world the block is in + * @param block the block + * @return if the block can be broken or placed by the player + */ + boolean check(Player player, GameWorld gameWorld, Block block); + +} diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/ConfigReader.java b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/ConfigReader.java index b5a44503..72167211 100644 --- a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/ConfigReader.java +++ b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/ConfigReader.java @@ -15,14 +15,19 @@ package de.erethon.dungeonsxl.api.dungeon; import de.erethon.caliburn.item.ExItem; +import de.erethon.caliburn.item.VanillaItem; import de.erethon.caliburn.mob.ExMob; import de.erethon.dungeonsxl.api.DungeonsAPI; +import de.erethon.dungeonsxl.api.world.GameWorld; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import org.bukkit.block.Block; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; /** * A functional interface to deserialize a raw value read from a configuration. @@ -79,6 +84,28 @@ public interface ConfigReader { } return map; }; + static final ConfigReader BUILD_MODE_READER = (api, value) -> { + if (value instanceof Boolean) { + return (Boolean) value ? BuildMode.TRUE : BuildMode.FALSE; + } else if (value instanceof String) { + return BuildMode.Registry.ENTRIES.get(((String) value).toLowerCase()); + } else if (value instanceof List) { + return (Player p, GameWorld w, Block b) -> ((List) value).contains(VanillaItem.get(b.getType()).getId()); + } else { + Map> whitelist = TOOL_BLOCK_MAP_READER.read(api, value); + if (whitelist == null) { + return null; + } + return (Player p, GameWorld w, Block b) -> { + ExItem type = VanillaItem.get(b.getType()); + ExItem breakTool = api.getCaliburn().getExItem(p.getItemInHand()); + return whitelist.containsKey(type) + && (whitelist.get(type) == null + || whitelist.get(type).isEmpty() + || whitelist.get(type).contains(breakTool)); + }; + } + }; /** * Reads a game rule state from the configuration. diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java index 8da1a8e6..c72b89db 100644 --- a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java +++ b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java @@ -14,7 +14,9 @@ */ package de.erethon.dungeonsxl.api.dungeon; +import de.erethon.caliburn.CaliburnAPI; import de.erethon.caliburn.item.ExItem; +import de.erethon.caliburn.item.VanillaItem; import de.erethon.caliburn.mob.ExMob; import de.erethon.commons.chat.MessageUtil; import de.erethon.commons.misc.EnumUtil; @@ -22,6 +24,7 @@ import de.erethon.commons.misc.NumberUtil; import de.erethon.dungeonsxl.api.DungeonsAPI; import de.erethon.dungeonsxl.api.Requirement; import de.erethon.dungeonsxl.api.Reward; +import de.erethon.dungeonsxl.api.world.GameWorld; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -33,7 +36,9 @@ import java.util.Map; import java.util.Set; import org.bukkit.Difficulty; import org.bukkit.GameMode; +import org.bukkit.block.Block; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; /** * Represents a game rule for a {@link Game}. @@ -99,16 +104,7 @@ public class GameRule { /** * If players can build and destroy blocks in this world. */ - public static final GameRule BREAK_BLOCKS = new GameRule<>(Boolean.class, "breakBlocks", false); - /** - * If players may destroy blocks they placed themselves. - */ - public static final GameRule BREAK_PLACED_BLOCKS = new GameRule<>(Boolean.class, "breakPlacedBlocks", false); - /** - * A whitelist of breakable blocks. breakBlocks is supposed to be set to "true" if this should be used. - */ - public static final GameRule>> BREAK_WHITELIST - = new MapGameRule<>("breakWhitelist", new HashMap<>(), ConfigReader.TOOL_BLOCK_MAP_READER, HashMap::new); + public static final GameRule BREAK_BLOCKS = new GameRule<>(BuildMode.class, "breakBlocks", BuildMode.FALSE, ConfigReader.BUILD_MODE_READER); /** * A blacklist of block types players cannot interact with. */ @@ -125,11 +121,7 @@ public class GameRule { /** * If blocks may be placed. */ - public static final GameRule PLACE_BLOCKS = new GameRule<>(Boolean.class, "placeBlocks", false); - /** - * A whitelist of placeable blocks. placeBlocks is supposed to be set to "true" if this should be used. - */ - public static final GameRule> PLACE_WHITELIST = new CollectionGameRule<>("placeWhitelist", new HashSet<>(), ConfigReader.EX_ITEM_SET_READER, HashSet::new); + public static final GameRule PLACE_BLOCKS = new GameRule<>(BuildMode.class, "placeBlocks", BuildMode.FALSE, ConfigReader.BUILD_MODE_READER); /** * A set of blocks that do not fade. * diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/world/GameWorld.java b/api/src/main/java/de/erethon/dungeonsxl/api/world/GameWorld.java index fca82544..9d8ef85f 100644 --- a/api/src/main/java/de/erethon/dungeonsxl/api/world/GameWorld.java +++ b/api/src/main/java/de/erethon/dungeonsxl/api/world/GameWorld.java @@ -20,6 +20,7 @@ import de.erethon.dungeonsxl.api.mob.DungeonMob; import de.erethon.dungeonsxl.api.player.PlayerGroup; import java.util.Collection; import org.bukkit.Location; +import org.bukkit.block.Block; /** * A playable resource instance. There may be any amount of GameWorlds per {@link ResourceWorld}. @@ -118,4 +119,11 @@ public interface GameWorld extends InstanceWorld { */ void setClassesEnabled(boolean enabled); + /** + * Returns a collection of the blocks that have been placed by players in the current game. + * + * @return a collection of the blocks that have been placed by players in the current game + */ + Collection getPlacedBlocks(); + } diff --git a/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java b/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java index cd920e9f..904fa4bc 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java +++ b/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java @@ -17,8 +17,6 @@ package de.erethon.dungeonsxl.world; import de.erethon.caliburn.CaliburnAPI; -import de.erethon.caliburn.item.ExItem; -import de.erethon.caliburn.item.VanillaItem; import de.erethon.dungeonsxl.DungeonsXL; import de.erethon.dungeonsxl.api.dungeon.Dungeon; import de.erethon.dungeonsxl.api.dungeon.Game; @@ -58,7 +56,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -69,6 +66,7 @@ import org.bukkit.entity.Hanging; import org.bukkit.entity.Player; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.inventory.ItemStack; +import de.erethon.dungeonsxl.api.dungeon.BuildMode; /** * @author Frank Baumann, Milan Albrecht, Daniel Saukel @@ -202,6 +200,11 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { return dSign; } + @Override + public Collection getPlacedBlocks() { + return placedBlocks; + } + /** * @return the placeableBlocks */ @@ -361,22 +364,21 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { public int getMobCount() { int mobCount = 0; - signs: - for (DungeonSign sign : getDungeonSigns().toArray(new DungeonSign[getDungeonSigns().size()])) { - if (!(sign instanceof MobSign)) { - continue; - } - - for (de.erethon.dungeonsxl.api.Trigger trigger : sign.getTriggers()) { - if (trigger instanceof ProgressTrigger) { - if (((ProgressTrigger) trigger).getFloorCount() > getGame().getFloorCount()) { - break signs; + signs: for (DungeonSign sign : getDungeonSigns().toArray(new DungeonSign[getDungeonSigns().size()])) { + if (!(sign instanceof MobSign)) { + continue; } - } - } - mobCount += ((MobSign) sign).getInitialAmount(); - } + for (de.erethon.dungeonsxl.api.Trigger trigger : sign.getTriggers()) { + if (trigger instanceof ProgressTrigger) { + if (((ProgressTrigger) trigger).getFloorCount() > getGame().getFloorCount()) { + break signs; + } + } + } + + mobCount += ((MobSign) sign).getInitialAmount(); + } return mobCount; } @@ -529,7 +531,9 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { return true; } - if (!getRules().getState(GameRule.BREAK_BLOCKS) && !getRules().getState(GameRule.BREAK_PLACED_BLOCKS)) { + BuildMode mode = getRules().getState(GameRule.BREAK_BLOCKS); + + if (mode == BuildMode.FALSE) { return true; } @@ -547,21 +551,7 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { } } - Map> whitelist = getRules().getState(GameRule.BREAK_WHITELIST); - ExItem material = VanillaItem.get(block.getType()); - ExItem breakTool = caliburn.getExItem(player.getItemInHand()); - - if (getRules().getState(GameRule.BREAK_PLACED_BLOCKS) && placedBlocks.contains(block)) { - return false; - } - if (whitelist != null && whitelist.containsKey(material) - && (whitelist.get(material) == null - || whitelist.get(material).isEmpty() - || whitelist.get(material).contains(breakTool))) { - return false; - } - - return true; + return !mode.check(player, this, block); } /** @@ -579,6 +569,10 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { return true; } + if (getRules().getState(GameRule.PLACE_BLOCKS).check(player, this, block)) { + return false; + } + PlaceableBlock placeableBlock = null; for (PlaceableBlock gamePlaceableBlock : placeableBlocks) { if (gamePlaceableBlock.canPlace(block, caliburn.getExItem(hand))) { @@ -586,7 +580,7 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { break; } } - if (!getRules().getState(GameRule.PLACE_BLOCKS) && placeableBlock == null) { + if (placeableBlock == null) { // Workaround for a bug that would allow 3-Block-high jumping Location loc = player.getLocation(); if (loc.getY() > block.getY() + 1.0 && loc.getY() <= block.getY() + 1.5) { @@ -599,20 +593,10 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { } } } - return true; } - if (placeableBlock != null) { - placeableBlock.onPlace(); - } - - Set whitelist = getRules().getState(GameRule.PLACE_WHITELIST); - if (whitelist.isEmpty() || whitelist.contains(VanillaItem.get(block.getType()))) { - placedBlocks.add(block); - return false; - } - - return true; + placeableBlock.onPlace(); + return false; } }