Improves button and material customization

Allows specifying a comma-separated list of materials and tags for a portal's button, open-material, closed-material and border blocks. A random value is used if more than one material is available.0
Uses the supplied button if any, instead of enforcing the specified button material.
Always protects the button against block breaking.
Fixes an incorrect permission result in the previous commit, which caused players stargate access to be inverted.
This commit is contained in:
2024-02-20 15:15:52 +01:00
parent b4a6ce1a77
commit a9e5855194
16 changed files with 385 additions and 154 deletions

View File

@@ -1,10 +1,9 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import org.bukkit.Bukkit;
import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier;
import net.knarcraft.stargate.config.material.MaterialSpecifier;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -26,15 +25,15 @@ public final class GateReader {
*
* @param scanner <p>The scanner to read from</p>
* @param characterMaterialMap <p>The map of characters to store valid symbols in</p>
* @param materialTagMap <p>The map of characters to store valid tag symbols in</p>
* @param fileName <p>The filename of the loaded gate config file</p>
* @param design <p>The list to store the loaded design/layout to</p>
* @param config <p>The map of config values to store to</p>
* @return <p>The column count/width of the loaded gate</p>
*/
public static int readGateFile(@NotNull Scanner scanner, @NotNull Map<Character, Material> characterMaterialMap,
@NotNull Map<Character, Tag<Material>> materialTagMap, @NotNull String fileName,
@NotNull List<List<Character>> design, Map<String, String> config) {
public static int readGateFile(@NotNull Scanner scanner,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialMap,
@NotNull String fileName, @NotNull List<List<Character>> design,
@NotNull Map<String, String> config) {
boolean designing = false;
int columns = 0;
try (scanner) {
@@ -43,14 +42,14 @@ public final class GateReader {
if (designing) {
//If we have reached the gate's layout/design, read it
columns = readGateDesignLine(line, columns, characterMaterialMap, materialTagMap, fileName, design);
columns = readGateDesignLine(line, columns, characterMaterialMap, fileName, design);
if (columns < 0) {
return -1;
}
} else {
if (!line.isEmpty() && !line.startsWith("#")) {
//Read a normal config value
readGateConfigValue(line, characterMaterialMap, materialTagMap, config);
readGateConfigValue(line, characterMaterialMap, config);
} else if ((line.isEmpty()) || (!line.contains("=") && !line.startsWith("#"))) {
//An empty line marks the start of the gate's layout/design
designing = true;
@@ -73,14 +72,12 @@ public final class GateReader {
* @param line <p>The line to read</p>
* @param maxColumns <p>The current max columns value of the design</p>
* @param characterMaterialMap <p>The map between characters and the corresponding materials to use</p>
* @param materialTagMap <p>The map between characters and the corresponding material tags to use</p>
* @param fileName <p>The filename of the loaded gate config file</p>
* @param design <p>The two-dimensional list to store the loaded design to</p>
* @return <p>The new max columns value of the design</p>
*/
private static int readGateDesignLine(@NotNull String line, int maxColumns,
@NotNull Map<Character, Material> characterMaterialMap,
@NotNull Map<Character, Tag<Material>> materialTagMap,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialMap,
@NotNull String fileName, @NotNull List<List<Character>> design) {
List<Character> row = new ArrayList<>();
@@ -91,7 +88,7 @@ public final class GateReader {
for (Character symbol : line.toCharArray()) {
//Refuse read gate designs with unknown characters
if (symbol.equals('?') || (!characterMaterialMap.containsKey(symbol) && !materialTagMap.containsKey(symbol))) {
if (symbol.equals('?') || !characterMaterialMap.containsKey(symbol)) {
Stargate.logSevere(String.format("Could not load Gate %s - Unknown symbol '%s' in diagram", fileName,
symbol));
return -1;
@@ -110,12 +107,11 @@ public final class GateReader {
*
* @param line <p>The line to read</p>
* @param characterMaterialMap <p>The character to material map to store to</p>
* @param materialTagMap <p>The character to material tag map to store to</p>
* @param config <p>The config value map to store to</p>
* @throws Exception <p>If an invalid material is encountered</p>
*/
private static void readGateConfigValue(@NotNull String line, @NotNull Map<Character, Material> characterMaterialMap,
@NotNull Map<Character, Tag<Material>> materialTagMap,
private static void readGateConfigValue(@NotNull String line,
@NotNull Map<Character, List<MaterialSpecifier>> characterMaterialMap,
@NotNull Map<String, String> config) throws Exception {
String[] split = line.split("=");
String key = split[0].trim();
@@ -125,23 +121,12 @@ public final class GateReader {
//Read a gate frame material
Character symbol = key.charAt(0);
if (value.startsWith("#")) {
String tagString = value.replaceFirst("#", "");
Tag<Material> tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft(tagString.toLowerCase()),
Material.class);
if (tag != null) {
materialTagMap.put(symbol, tag);
return;
}
List<MaterialSpecifier> materials = MaterialHelper.parseTagsAndMaterials(value);
if (!materials.isEmpty()) {
characterMaterialMap.put(symbol, materials);
} else {
Material material = Material.matchMaterial(value);
if (material != null) {
//Register the map between the read symbol and the corresponding material
characterMaterialMap.put(symbol, material);
return;
}
throw new Exception("Invalid material in line: " + line);
}
throw new Exception("Invalid material in line: " + line);
} else {
//Read a normal config value
config.put(key, value);
@@ -180,17 +165,17 @@ public final class GateReader {
* @return <p>The material specified in the config, or the default material if it could not be read</p>
*/
@NotNull
public static Material readGateConfig(@NotNull Map<String, String> config, @NotNull String fileName,
@NotNull String key, @NotNull Material defaultMaterial) {
public static List<MaterialSpecifier> readGateConfig(@NotNull Map<String, String> config, @NotNull String fileName,
@NotNull String key, @NotNull Material defaultMaterial) {
if (config.containsKey(key)) {
Material material = Material.matchMaterial(config.get(key));
if (material != null) {
return material;
List<MaterialSpecifier> materialSpecifiers = MaterialHelper.parseTagsAndMaterials(config.get(key));
if (!materialSpecifiers.isEmpty()) {
return materialSpecifiers;
} else {
Stargate.logWarning(String.format("Error reading %s: %s is not a material", fileName, key));
}
}
return defaultMaterial;
return List.of(new BukkitMaterialSpecifier(defaultMaterial));
}
/**

View File

@@ -0,0 +1,28 @@
package net.knarcraft.stargate.utility;
import java.util.List;
import java.util.Random;
/**
* A helper class for dealing with lists
*/
public final class ListHelper {
private static final Random random = new Random();
private ListHelper() {
}
/**
* Gets a random item from a list
*
* @param list <p>The list to get an item from</p>
* @param <T> <p>The type of item the list contains</p>
* @return <p>A random item</p>
*/
public static <T> T getRandom(List<T> list) {
return list.get(random.nextInt(list.size()));
}
}

View File

@@ -1,8 +1,19 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier;
import net.knarcraft.stargate.config.material.BukkitTagSpecifier;
import net.knarcraft.stargate.config.material.MaterialSpecifier;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This class helps decide properties of materials not already present in the Spigot API
@@ -50,4 +61,80 @@ public final class MaterialHelper {
return Tag.BUTTONS.isTagged(material) || isWallCoral(material) || isContainer(material);
}
@NotNull
public static String specifiersToString(@NotNull List<MaterialSpecifier> specifiers) {
List<String> names = new ArrayList<>();
for (MaterialSpecifier specifier : specifiers) {
names.add(specifier.asString());
}
return String.join(",", names);
}
/**
* Converts a list of material specifiers to a set of materials
*
* @param specifiers <p>The material specifiers to convert</p>
* @return <p>The materials the specifiers represent</p>
*/
@NotNull
public static Set<Material> specifiersToMaterials(@NotNull List<MaterialSpecifier> specifiers) {
Set<Material> output = new HashSet<>();
for (MaterialSpecifier specifier : specifiers) {
output.addAll(specifier.asMaterials());
}
return output;
}
/**
* Parses all materials and material tags found in the input string
*
* @param input <p>The input string to parse</p>
* @return <p>All material specifiers found</p>
*/
@NotNull
public static List<MaterialSpecifier> parseTagsAndMaterials(@NotNull String input) {
List<MaterialSpecifier> specifiers = new ArrayList<>();
// Nothing to parse
if (input.isBlank()) {
return specifiers;
}
String[] parts;
if (input.contains(",")) {
parts = input.split(",");
} else {
parts = new String[]{input};
}
for (String part : parts) {
MaterialSpecifier materialSpecifier = parseTagOrMaterial(part.trim());
if (materialSpecifier != null) {
specifiers.add(materialSpecifier);
}
}
return specifiers;
}
@Nullable
private static MaterialSpecifier parseTagOrMaterial(@NotNull String input) {
if (input.startsWith("#")) {
String tagString = input.replaceFirst("#", "").toLowerCase();
Tag<Material> tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft(tagString), Material.class);
if (tag != null) {
return new BukkitTagSpecifier(tag);
}
} else {
Material material = Material.matchMaterial(input);
if (material != null) {
return new BukkitMaterialSpecifier(material);
}
}
return null;
}
}

View File

@@ -409,11 +409,13 @@ public final class PermissionHelper {
}
//Player cannot access portal
if (!PermissionHelper.cannotAccessPortal(player, entrancePortal, destination)) {
if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destination)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
Stargate.debug("PermissionHelper::playerCannotTeleport", "Closed portal because player is " +
"missing necessary permissions");
entrancePortal.getPortalOpener().closePortal(false);
return true;
}

View File

@@ -27,6 +27,7 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
import static net.knarcraft.stargate.portal.PortalSignDrawer.markPortalWithInvalidGate;
@@ -399,9 +400,15 @@ public final class PortalFileHelper {
return;
}
Directional buttonData = (Directional) Bukkit.createBlockData(portal.getGate().getPortalButton());
buttonData.setFacing(buttonFacing);
button.getBlock().setBlockData(buttonData);
if (!MaterialHelper.isButtonCompatible(button.getType())) {
@NotNull List<Material> possibleMaterials = MaterialHelper.specifiersToMaterials(
portal.getGate().getPortalButtonMaterials()).stream().toList();
Material buttonType = ListHelper.getRandom(possibleMaterials);
Directional buttonData = (Directional) Bukkit.createBlockData(buttonType);
buttonData.setFacing(buttonFacing);
button.getBlock().setBlockData(buttonData);
}
portal.getStructure().setButton(button);
}