diff --git a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java index 2676dad5..50fba0cc 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java +++ b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java @@ -703,46 +703,12 @@ public final class GameModeConfig { public final double MaxDesignBlastResistance; /** - * List of limited material (combinations)
- * List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format) - */ - public final Map, Integer> Limited; - - /** - * Item amount limits per inventory (container) type.
- * Key: inventory material (e.g. DISPENSER, CHEST, ...). Value: item material -> maximal - * amount of that item allowed to be stored in a single inventory of that type.
- * An item is only allowed to be placed in an inventory at all if it appears here for that - * inventory's material and item. Every material returned by - * {@link SQLWrapper#getInventoryMaterials()} is always present as a key - with an empty value - * map if not explicitly configured - so every real container is always scanned by the schematic - * checker for forbidden contents, it just allows no items by default.
- * Configured via the {@code ItemsInInventory} list, entries consisting of {@code Inventories} - * (list of inventory materials or group names - {@code storage} for - * {@link SQLWrapper#getStorageMaterials()}, {@code all} for every material from - * {@link SQLWrapper#getInventoryMaterials()}), {@code Items} (list of item materials, each - * checked independently against {@code Amount} - not summed) and {@code Amount}. + * List of limited block materials * - * @implSpec No item is allowed in any inventory unless configured here + * @implSpec {@link SQLWrapper#getInventoryMaterials)} are all disallowed by default to have any content but are unlimited in number. + * @implSpec Any Block that has more Blast Resistance than {@link #MaxBlastResistance} will be defaulted to 0. */ - public final Map> ItemsInInventory; - - /** - * Resolves one {@code Inventories} entry to the concrete inventory materials it denotes: - * {@code storage} for {@link SQLWrapper#getStorageMaterials()}, {@code all} for - * {@link SQLWrapper#getInventoryMaterials()}, or the material itself otherwise. - */ - private Set expandInventoryGroup(YMLWrapper loader, String name) { - switch (name.toUpperCase()) { - case "STORAGE": - return (Set) SQLWrapper.impl.getStorageMaterials(); - case "ALL": - return (Set) SQLWrapper.impl.getInventoryMaterials(); - default: - M material = loader.materialMapper.apply(name.toUpperCase()); - return material != null ? Collections.singleton(material) : Collections.emptySet(); - } - } + public final List> Limited; private SchematicConfig(YMLWrapper loader) { loaded = loader.canLoad(); @@ -763,52 +729,142 @@ public final class GameModeConfig { MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE); MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", MaxBlastResistance); - Map, Integer> Limited = new HashMap<>(); - for (Map entry : loader.getMapList("Limited")) { - int amount = (Integer) entry.get("Amount"); - Set materials = new HashSet<>((List) entry.get("Materials")); - if (amount == 0) { - materials.forEach(material -> { - Limited.put(Collections.singleton(loader.materialMapper.apply(material.toUpperCase())), 0); - }); - } else { - Limited.put(Collections.unmodifiableSet(materials.stream().map(String::toUpperCase).map(loader.materialMapper).collect(Collectors.toSet())), amount); - } + List> limited = new ArrayList<>(); + Set blocks = new HashSet<>(); + for (YMLWrapper limitedLoader : loader.withAsList("Limited")) { + BlockRule blockRule = new BlockRule<>(limitedLoader); + blocks.addAll(blockRule.Blocks); + limited.add(blockRule); } - SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance).forEach(material -> { - if (Limited.entrySet().stream().anyMatch(entry -> entry.getKey().contains(material))) return; - Limited.put(Collections.singleton((M) material), 0); - }); - this.Limited = Collections.unmodifiableMap(Limited); - - Map> itemsInInventory = new HashMap<>(); - for (Map entry : loader.getMapList("ItemsInInventory")) { - int amount = (Integer) entry.get("Amount"); - List inventories = (List) entry.get("Inventories"); - List items = (List) entry.get("Items"); - for (String inventoryName : inventories) { - for (M inventoryMaterial : expandInventoryGroup(loader, inventoryName)) { - if (inventoryMaterial == null) continue; - Map itemCaps = itemsInInventory.computeIfAbsent(inventoryMaterial, m -> new HashMap<>()); - for (String itemName : items) { - M itemMaterial = loader.materialMapper.apply(itemName.toUpperCase()); - if (itemMaterial == null) continue; - itemCaps.put(itemMaterial, amount); - } - } - } + for (M material : (List) SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance)) { + if (material == null || !blocks.add(material)) continue; + limited.add(new BlockRule<>(material, 0)); } - for (M allMaterial : (Set) SQLWrapper.impl.getInventoryMaterials()) { - if (allMaterial == null) continue; - itemsInInventory.computeIfAbsent(allMaterial, m -> new HashMap<>()); + for (M material : (Set) SQLWrapper.impl.getInventoryMaterials()) { + if (material == null || !blocks.add(material)) continue; + limited.add(new BlockRule<>(material, Integer.MAX_VALUE)); } - this.ItemsInInventory = Collections.unmodifiableMap(itemsInInventory.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> Collections.unmodifiableMap(e.getValue())))); + this.Limited = Collections.unmodifiableList(limited); this.ReplacementsWithoutBlockUpdates = loader.getMap("ReplacementsWithoutBlockUpdates", loader.materialMapper, loader.materialMapper); this.ReplacementsWithBlockUpdates = loader.getMap("ReplacementsWithBlockUpdates", loader.materialMapper, loader.materialMapper); } + public int getMaxCount(M material) { + return Limited.stream() + .filter(rule -> rule.Blocks.contains(material)) + .mapToInt(rule -> rule.MaxCount) + .max() + .orElse(Integer.MAX_VALUE); + } + + public boolean isInventory(M material) { + return Limited.stream() + .filter(rule -> rule.Blocks.contains(material)) + .anyMatch(rule -> rule.Content.loaded); + } + + public Integer getItemAmount(M material, M item) { + return Limited.stream() + .filter(rule -> rule.Blocks.contains(material)) + .filter(rule -> rule.Content.Items.contains(item)) + .mapToInt(rule -> rule.Content.Amount) + .max() + .orElse(0); + } + + @ToString + public static final class BlockRule { + + /** + * The block materials this rule applies to + */ + public final Set Blocks; + + /** + * Maximal amount of Blocks allowed in the schematic + * + * @implSpec {@code 0} by default + */ + public final int MaxCount; + + /** + * The item content filter for Blocks + * + * @implSpec no Items with {@code Amount 0} (denies any item) by default + */ + public final Content Content; + + /** + * Resolves one {@code Materials} entry to the concrete materials it denotes: {@code storage} + * for {@link SQLWrapper#getStorageMaterials()}, {@code all} for + * {@link SQLWrapper#getInventoryMaterials()}, or the material itself otherwise. + */ + private Set expandInventoryGroup(YMLWrapper loader, String name) { + switch (name.toUpperCase()) { + case "STORAGE": + return (Set) SQLWrapper.impl.getStorageMaterials(); + case "INVENTORY": + return (Set) SQLWrapper.impl.getInventoryMaterials(); + default: + M material = loader.materialMapper.apply(name.toUpperCase()); + return material != null ? Collections.singleton(material) : Collections.emptySet(); + } + } + + private BlockRule(YMLWrapper loader) { + List blocks = loader.getStringList("Blocks") + .stream() + .flatMap(value -> expandInventoryGroup(loader, value).stream()) + .collect(Collectors.toList()); + if (blocks.isEmpty()) { // Legacy support + blocks = loader.getMaterialList("Materials"); + } + Blocks = Collections.unmodifiableSet(new HashSet<>(blocks)); + MaxCount = loader.getInt("MaxCount", // Legacy support + loader.getInt("Amount", 0) + ); + Content = new Content<>(loader.with("Content")); + } + + private BlockRule(M block, int maxCount) { + Blocks = Collections.singleton(block); + MaxCount = maxCount; + Content = new Content<>(); + } + + @ToString + public static final class Content { + + public final boolean loaded; + + /** + * Items allowed by this rule + */ + public final Set Items; + + /** + * Maximal amount of an allowed item per single block instance + * + * @implSpec {@code 0} by default + */ + public final int Amount; + + private Content(YMLWrapper loader) { + loaded = loader.canLoad(); + List items = loader.getMaterialList("Items"); + Items = Collections.unmodifiableSet(new HashSet<>(items)); + Amount = loader.getInt("Amount", 0); + } + + private Content() { + loaded = false; + Items = new HashSet<>(); + Amount = 0; + } + } + } + @ToString public static final class SizeConfig { diff --git a/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java b/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java index be52e8fb..565687fe 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java +++ b/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java @@ -39,7 +39,7 @@ public interface SQLWrapper { /** * Storage-type inventory materials (chests, barrels, shulker boxes of any color) - the - * {@code storage} group usable in {@code GameModeConfig}'s {@code ItemsInInventory}. + * {@code storage} group usable in {@code GameModeConfig}'s {@code Limited}. */ default Set getStorageMaterials() { return Collections.emptySet(); @@ -47,7 +47,7 @@ public interface SQLWrapper { /** * Every inventory material - the {@code all} group usable in {@code GameModeConfig}'s - * {@code ItemsInInventory}. Expected to be a superset of {@link #getStorageMaterials()}. + * {@code Limited}. Expected to be a superset of {@link #getStorageMaterials()}. */ default Set getInventoryMaterials() { return Collections.emptySet(); diff --git a/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java b/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java index 567524ff..87df1352 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java +++ b/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java @@ -76,6 +76,25 @@ final class YMLWrapper { return new YMLWrapper<>(false, Collections.emptyMap(), materialMapper, winconditionMapper); } + public List> withAsList(String path) { + if (document.containsKey(path)) { + Object value = document.get(path); + if (value instanceof List) { + List list = (List) value; + List maps = list.stream() + .filter(Map.class::isInstance) + .map(Map.class::cast) + .collect(Collectors.toList()); + if (maps.size() == list.size()) { + return (List) maps.stream() + .map(map -> new YMLWrapper(true, map, materialMapper, winconditionMapper)) + .collect(Collectors.toList()); + } + } + } + return Collections.emptyList(); + } + public T get(String path, T defaultValue, Function mapper) { Object value = this.document.get(path); if (value == null) return defaultValue; diff --git a/FightSystem/FightSystem_Core/src/config.yml b/FightSystem/FightSystem_Core/src/config.yml index 8987b88a..dbaf3c33 100644 --- a/FightSystem/FightSystem_Core/src/config.yml +++ b/FightSystem/FightSystem_Core/src/config.yml @@ -109,30 +109,21 @@ Schematic: MaxBlocks: 0 # defaults to 0 (ignored) if missing # Maximal blast resistance for the design blocks MaxDesignBlastResistance: 100000000 # defaults to Double.MAX_VALUE if missing - # List of limited material (combinations) - # List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format) + # List of limited block materials + # InventoryMaterials are all disallowed by default to have any content but are unlimited in number + # Any Block that has more Blast Resistance than MaxBlastResistance will be defaulted to 0. Limited: - - Materials: [ ] - Amount: 0 - # Item amount limits per inventory (container) type. - # An item is only allowed to be placed in an inventory at all if it appears here for that - # inventory's material (a small set of decorative items, e.g. flowers, is always allowed - # everywhere regardless of this list) - NOTHING is allowed in any inventory unless listed here. - # Each entry contains Inventories (list of inventory materials, or a group name - 'storage' for - # chests/barrels/shulker boxes of any color, 'all' for every scanned inventory material including - # dispensers, droppers, hoppers, furnaces, etc.), Items (list of item materials, each checked - # independently against Amount - not summed) and Amount (max stored amount of that item per - # single inventory of that type). - ItemsInInventory: # defaults to none (nothing allowed in any inventory) if missing - - Inventories: [ DISPENSER ] - Items: [ BUCKET ] - Amount: 1 - - Inventories: [ DISPENSER ] - Items: [ ARROW, FIRE_CHARGE ] - Amount: 128 - - Inventories: [ storage ] - Items: [ TNT ] - Amount: 1728 + - Blocks: [ DISPENSER ] + MaxCount: 64 + - Blocks: [ DISPENSER ] + MaxCount: 16 + Content: + Items: [ ARROW, FIRE_CHARGE ] + Amount: 128 + - Blocks: [ storage ] + Content: + Items: [ TNT ] + Amount: 1728 # The name of the game mode presented to the players GameName: WarGear # defaults to WarGear if missing diff --git a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java index 03d5e182..cb23bbbd 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java @@ -75,7 +75,7 @@ public class AutoChecker { BlockPos pos = new BlockPos(x, y, z); - if (type.Schematic.ItemsInInventory.containsKey(material)) { + if (type.Schematic.isInventory(material)) { checkInventory(result, block, material, pos, type); if (!result.getInventoryItemCounts().getOrDefault(pos, Collections.emptyMap()).isEmpty()) { result.getBlockCounts().merge(material, 1, Integer::sum); @@ -109,7 +109,6 @@ public class AutoChecker { List items = nbt.getList("Items", CompoundTag.class); if (items.isEmpty()) return; // Leeres Inventar - Map itemCaps = type.Schematic.ItemsInInventory.getOrDefault(material, Collections.emptyMap()); Map itemCounts = new EnumMap<>(Material.class); int windChargeCount = 0; for (CompoundTag item : items) { @@ -124,7 +123,7 @@ public class AutoChecker { if (type.Schematic.Type.getName().equals("wargearseason26") && material == Material.DISPENSER && itemType == Material.WIND_CHARGE) { windChargeCount += item.getInt("count"); - } else if (!itemCaps.containsKey(itemType)) { + } else if (type.Schematic.getItemAmount(material, itemType) == 0) { result.getForbiddenItems().computeIfAbsent(pos, blockVector3 -> new HashSet<>()).add(itemType); } else { itemCounts.merge(itemType, item.getInt("count"), Integer::sum); diff --git a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoCheckerResult.java b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoCheckerResult.java index 090530d8..c59f17ab 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoCheckerResult.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoCheckerResult.java @@ -29,7 +29,6 @@ import org.bukkit.Material; import org.bukkit.entity.Player; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -75,9 +74,10 @@ public class AutoCheckerResult { public boolean isInventoryItemsOK() { return blockScanResult.getInventoryItemCounts().entrySet().stream().allMatch(posEntry -> { Material inventory = blockScanResult.getInventoryBlockType().get(posEntry.getKey()); - Map caps = type.Schematic.ItemsInInventory.getOrDefault(inventory, Collections.emptyMap()); - return posEntry.getValue().entrySet().stream() - .allMatch(itemEntry -> itemEntry.getValue() <= caps.getOrDefault(itemEntry.getKey(), Integer.MAX_VALUE)); + return posEntry.getValue().entrySet().stream().allMatch(itemEntry -> { + int cap = type.Schematic.getItemAmount(inventory, itemEntry.getKey()); + return itemEntry.getValue() <= cap; + }); }); } @@ -106,13 +106,8 @@ public class AutoCheckerResult { } public boolean isLimitedBlocksOK() { - try { - return type.Schematic.Limited.entrySet().stream() - .map(setIntegerEntry -> setIntegerEntry.getKey().stream().map(blockScanResult.getBlockCounts()::get).map(i -> i == null || i <= setIntegerEntry.getValue()).reduce(Boolean::logicalAnd).orElse(false)) - .reduce(Boolean::logicalAnd).orElse(true); - } catch (NullPointerException e) { - return false; - } + return blockScanResult.getBlockCounts().entrySet().stream() + .allMatch(entry -> entry.getValue() <= type.Schematic.getMaxCount(entry.getKey())); } public boolean isDesignBlastResistanceOK() { @@ -136,14 +131,13 @@ public class AutoCheckerResult { SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_BLOCKS", p, blockScanResult.getBlockCounts().values().stream().reduce(Integer::sum).orElse(0), type.Schematic.MaxBlocks); } if (!isLimitedBlocksOK()) { - type.Schematic.Limited.forEach((materials, integer) -> { - for (Material mat : materials) { - if (mat != null && blockScanResult.getBlockCounts().getOrDefault(mat, 0) > integer) { - if (integer == 0) { - SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK", p, mat.name()); - } else { - SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_BLOCK", p, mat.name(), blockScanResult.getBlockCounts().getOrDefault(mat, 0), integer); - } + blockScanResult.getBlockCounts().forEach((mat, count) -> { + int maxCount = type.Schematic.getMaxCount(mat); + if (count > maxCount) { + if (maxCount == 0) { + SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK", p, mat.name()); + } else { + SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_BLOCK", p, mat.name(), count, maxCount); } } }); @@ -163,9 +157,8 @@ public class AutoCheckerResult { blockScanResult.getInventoryItemCounts().forEach((pos, itemCounts) -> { Material inventory = blockScanResult.getInventoryBlockType().get(pos); - Map caps = type.Schematic.ItemsInInventory.getOrDefault(inventory, Collections.emptyMap()); itemCounts.forEach((itemType, count) -> { - int cap = caps.getOrDefault(itemType, Integer.MAX_VALUE); + int cap = type.Schematic.getItemAmount(inventory, itemType); if (count <= cap) return; SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_INVENTORY_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(pos), pos.getBlockX(), diff --git a/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java b/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java index ffd14519..198a3689 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java @@ -200,11 +200,10 @@ public class SchematicCommand extends SWCommand { for (Map.Entry> posEntry : result.getBlockScanResult().getInventoryItemCounts().entrySet()) { BlockPos pos = posEntry.getKey(); Material inventory = result.getBlockScanResult().getInventoryBlockType().get(pos); - Map caps = type.Schematic.ItemsInInventory.getOrDefault(inventory, Collections.emptyMap()); for (Map.Entry itemEntry : posEntry.getValue().entrySet()) { Material itemType = itemEntry.getKey(); - int cap = caps.getOrDefault(itemType, Integer.MAX_VALUE); + Integer cap = type.Schematic.getItemAmount(inventory, itemType); if (itemEntry.getValue() <= cap) { continue; } @@ -243,9 +242,9 @@ public class SchematicCommand extends SWCommand { } if (!result.isLimitedBlocksOK()) { - Set toReplace = type.Schematic.Limited.entrySet().stream() - .filter(setIntegerEntry -> setIntegerEntry.getValue() == 0) - .flatMap(setIntegerEntry -> setIntegerEntry.getKey().stream()) + Set toReplace = type.Schematic.Limited.stream() + .filter(rule -> rule.MaxCount == 0) + .flatMap(rule -> rule.Blocks.stream()) .collect(Collectors.toSet()); BlockState replaceType = Objects.requireNonNull(toReplace.contains(Material.END_STONE) ? BlockTypes.IRON_BLOCK : BlockTypes.END_STONE).getDefaultState(); BlockVector3 min = clipboard.getMinimumPoint();