Improve AutoChecker and GameModeConfig again

This commit is contained in:
2026-08-26 21:46:45 +02:00
parent 5a52771ec1
commit 38e845b0e4
7 changed files with 187 additions and 130 deletions
@@ -703,46 +703,12 @@ public final class GameModeConfig<M, W> {
public final double MaxDesignBlastResistance;
/**
* List of limited material (combinations)<br/>
* List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format)
*/
public final Map<Set<M>, Integer> Limited;
/**
* Item amount limits per inventory (container) type.<br/>
* Key: inventory material (e.g. DISPENSER, CHEST, ...). Value: item material -&gt; maximal
* amount of that item allowed to be stored in a single inventory of that type.<br/>
* 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.<br/>
* 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<M, Map<M, Integer>> 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<M> expandInventoryGroup(YMLWrapper<M, ?> loader, String name) {
switch (name.toUpperCase()) {
case "STORAGE":
return (Set<M>) SQLWrapper.impl.getStorageMaterials();
case "ALL":
return (Set<M>) SQLWrapper.impl.getInventoryMaterials();
default:
M material = loader.materialMapper.apply(name.toUpperCase());
return material != null ? Collections.singleton(material) : Collections.emptySet();
}
}
public final List<BlockRule<M>> Limited;
private SchematicConfig(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad();
@@ -763,52 +729,142 @@ public final class GameModeConfig<M, W> {
MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE);
MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", MaxBlastResistance);
Map<Set<M>, Integer> Limited = new HashMap<>();
for (Map<?, ?> entry : loader.getMapList("Limited")) {
int amount = (Integer) entry.get("Amount");
Set<String> materials = new HashSet<>((List<String>) 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<BlockRule<M>> limited = new ArrayList<>();
Set<M> blocks = new HashSet<>();
for (YMLWrapper<M, ?> limitedLoader : loader.withAsList("Limited")) {
BlockRule<M> 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<M, Map<M, Integer>> itemsInInventory = new HashMap<>();
for (Map<?, ?> entry : loader.getMapList("ItemsInInventory")) {
int amount = (Integer) entry.get("Amount");
List<String> inventories = (List<String>) entry.get("Inventories");
List<String> items = (List<String>) entry.get("Items");
for (String inventoryName : inventories) {
for (M inventoryMaterial : expandInventoryGroup(loader, inventoryName)) {
if (inventoryMaterial == null) continue;
Map<M, Integer> 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<M>) SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance)) {
if (material == null || !blocks.add(material)) continue;
limited.add(new BlockRule<>(material, 0));
}
for (M allMaterial : (Set<M>) SQLWrapper.impl.getInventoryMaterials()) {
if (allMaterial == null) continue;
itemsInInventory.computeIfAbsent(allMaterial, m -> new HashMap<>());
for (M material : (Set<M>) 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<M> {
/**
* The block materials this rule applies to
*/
public final Set<M> 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<M> 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<M> expandInventoryGroup(YMLWrapper<M, ?> loader, String name) {
switch (name.toUpperCase()) {
case "STORAGE":
return (Set<M>) SQLWrapper.impl.getStorageMaterials();
case "INVENTORY":
return (Set<M>) SQLWrapper.impl.getInventoryMaterials();
default:
M material = loader.materialMapper.apply(name.toUpperCase());
return material != null ? Collections.singleton(material) : Collections.emptySet();
}
}
private BlockRule(YMLWrapper<M, ?> loader) {
List<M> 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<M> {
public final boolean loaded;
/**
* Items allowed by this rule
*/
public final Set<M> Items;
/**
* Maximal amount of an allowed item per single block instance
*
* @implSpec {@code 0} by default
*/
public final int Amount;
private Content(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad();
List<M> 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 {
@@ -39,7 +39,7 @@ public interface SQLWrapper<M> {
/**
* 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<M> getStorageMaterials() {
return Collections.emptySet();
@@ -47,7 +47,7 @@ public interface SQLWrapper<M> {
/**
* 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<M> getInventoryMaterials() {
return Collections.emptySet();
@@ -76,6 +76,25 @@ final class YMLWrapper<M, W> {
return new YMLWrapper<>(false, Collections.emptyMap(), materialMapper, winconditionMapper);
}
public List<YMLWrapper<M, W>> withAsList(String path) {
if (document.containsKey(path)) {
Object value = document.get(path);
if (value instanceof List) {
List<?> list = (List<?>) value;
List<Map> 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> T get(String path, T defaultValue, Function<Object, T> mapper) {
Object value = this.document.get(path);
if (value == null) return defaultValue;