Rewrite break / place game rules; resolves #1041

This commit is contained in:
Daniel Saukel 2021-10-10 16:23:02 +02:00
parent 16f138aadd
commit 2c7ae708ec
5 changed files with 143 additions and 61 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, BuildMode> 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.
* <p>
* 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);
}

View File

@ -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<V> {
}
return map;
};
static final ConfigReader<BuildMode> 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<ExItem, HashSet<ExItem>> 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.

View File

@ -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<V> {
/**
* If players can build and destroy blocks in this world.
*/
public static final GameRule<Boolean> BREAK_BLOCKS = new GameRule<>(Boolean.class, "breakBlocks", false);
/**
* If players may destroy blocks they placed themselves.
*/
public static final GameRule<Boolean> 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<Map<ExItem, HashSet<ExItem>>> BREAK_WHITELIST
= new MapGameRule<>("breakWhitelist", new HashMap<>(), ConfigReader.TOOL_BLOCK_MAP_READER, HashMap::new);
public static final GameRule<BuildMode> 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<V> {
/**
* If blocks may be placed.
*/
public static final GameRule<Boolean> 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<Set<ExItem>> PLACE_WHITELIST = new CollectionGameRule<>("placeWhitelist", new HashSet<>(), ConfigReader.EX_ITEM_SET_READER, HashSet::new);
public static final GameRule<BuildMode> PLACE_BLOCKS = new GameRule<>(BuildMode.class, "placeBlocks", BuildMode.FALSE, ConfigReader.BUILD_MODE_READER);
/**
* A set of blocks that do not fade.
*

View File

@ -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<Block> getPlacedBlocks();
}

View File

@ -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<Block> 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<ExItem, HashSet<ExItem>> 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<ExItem> 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;
}
}