From a9e5855194f73fe4cb4387d343d3c9216babdbae Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Tue, 20 Feb 2024 15:15:52 +0100 Subject: [PATCH] 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. --- .../material/BukkitMaterialSpecifier.java | 36 ++++++ .../config/material/BukkitTagSpecifier.java | 35 ++++++ .../config/material/MaterialSpecifier.java | 29 +++++ .../stargate/listener/BlockEventListener.java | 8 ++ .../listener/PlayerEventListener.java | 3 +- .../stargate/portal/PortalActivator.java | 4 +- .../stargate/portal/PortalCreator.java | 9 +- .../stargate/portal/PortalHandler.java | 6 +- .../stargate/portal/PortalOpener.java | 19 ++- .../stargate/portal/property/gate/Gate.java | 117 +++++++----------- .../portal/property/gate/GateHandler.java | 82 ++++++------ .../stargate/utility/GateReader.java | 59 ++++----- .../stargate/utility/ListHelper.java | 28 +++++ .../stargate/utility/MaterialHelper.java | 87 +++++++++++++ .../stargate/utility/PermissionHelper.java | 4 +- .../stargate/utility/PortalFileHelper.java | 13 +- 16 files changed, 385 insertions(+), 154 deletions(-) create mode 100644 src/main/java/net/knarcraft/stargate/config/material/BukkitMaterialSpecifier.java create mode 100644 src/main/java/net/knarcraft/stargate/config/material/BukkitTagSpecifier.java create mode 100644 src/main/java/net/knarcraft/stargate/config/material/MaterialSpecifier.java create mode 100644 src/main/java/net/knarcraft/stargate/utility/ListHelper.java diff --git a/src/main/java/net/knarcraft/stargate/config/material/BukkitMaterialSpecifier.java b/src/main/java/net/knarcraft/stargate/config/material/BukkitMaterialSpecifier.java new file mode 100644 index 0000000..a761d5c --- /dev/null +++ b/src/main/java/net/knarcraft/stargate/config/material/BukkitMaterialSpecifier.java @@ -0,0 +1,36 @@ +package net.knarcraft.stargate.config.material; + +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * A specifier for a Bukkit material + */ +public class BukkitMaterialSpecifier implements MaterialSpecifier { + + private final Material material; + + /** + * Instantiates a new material specifier + * + * @param material

The material to specify

+ */ + public BukkitMaterialSpecifier(@NotNull Material material) { + this.material = material; + } + + @Override + @NotNull + public String asString() { + return this.material.name(); + } + + @Override + @NotNull + public Set asMaterials() { + return Set.of(this.material); + } + +} diff --git a/src/main/java/net/knarcraft/stargate/config/material/BukkitTagSpecifier.java b/src/main/java/net/knarcraft/stargate/config/material/BukkitTagSpecifier.java new file mode 100644 index 0000000..8e4f6f4 --- /dev/null +++ b/src/main/java/net/knarcraft/stargate/config/material/BukkitTagSpecifier.java @@ -0,0 +1,35 @@ +package net.knarcraft.stargate.config.material; + +import org.bukkit.Material; +import org.bukkit.Tag; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * A specifier for a Bukkit material tag + */ +public class BukkitTagSpecifier implements MaterialSpecifier { + + private final Tag tag; + + /** + * Instantiates a new tag specifier + * + * @param tag

The tag to specify

+ */ + public BukkitTagSpecifier(@NotNull Tag tag) { + this.tag = tag; + } + + @Override + public @NotNull String asString() { + return "#" + this.tag.getKey().toString().replaceFirst("minecraft:", ""); + } + + @Override + public @NotNull Set asMaterials() { + return this.tag.getValues(); + } + +} diff --git a/src/main/java/net/knarcraft/stargate/config/material/MaterialSpecifier.java b/src/main/java/net/knarcraft/stargate/config/material/MaterialSpecifier.java new file mode 100644 index 0000000..9665c05 --- /dev/null +++ b/src/main/java/net/knarcraft/stargate/config/material/MaterialSpecifier.java @@ -0,0 +1,29 @@ +package net.knarcraft.stargate.config.material; + +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * An interface describing a specifier for one or more Bukkit materials + */ +public interface MaterialSpecifier { + + /** + * Gets the string representation of the material specifier + * + *

This is used when saving the value to a gate file

+ */ + @NotNull + String asString(); + + /** + * Gets all the materials the specifier specifies + * + *

This is used when registering gate materials

+ */ + @NotNull + Set asMaterials(); + +} diff --git a/src/main/java/net/knarcraft/stargate/listener/BlockEventListener.java b/src/main/java/net/knarcraft/stargate/listener/BlockEventListener.java index 25230f8..78e0e37 100644 --- a/src/main/java/net/knarcraft/stargate/listener/BlockEventListener.java +++ b/src/main/java/net/knarcraft/stargate/listener/BlockEventListener.java @@ -2,6 +2,7 @@ package net.knarcraft.stargate.listener; import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.container.BlockChangeRequest; +import net.knarcraft.stargate.container.BlockLocation; import net.knarcraft.stargate.event.StargateDestroyEvent; import net.knarcraft.stargate.portal.Portal; import net.knarcraft.stargate.portal.PortalCreator; @@ -139,6 +140,13 @@ public class BlockEventListener implements Listener { boolean deny = false; String denyMessage = ""; + // Block breaking the button from breaking the entire Stargate + if (portal.getStructure().getButton() != null && portal.getStructure().getButton().equals( + new BlockLocation(event.getBlock()))) { + event.setCancelled(true); + return; + } + //Decide if the user can destroy the portal if (!PermissionHelper.canDestroyPortal(player, portal)) { denyMessage = Stargate.getString("denyMsg"); diff --git a/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java b/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java index 4720def..f75fc36 100644 --- a/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java +++ b/src/main/java/net/knarcraft/stargate/listener/PlayerEventListener.java @@ -181,7 +181,8 @@ public class PlayerEventListener implements Listener { //Check an additional block away for BungeeCord portals using END_PORTAL as its material entrancePortal = PortalHandler.getByAdjacentEntrance(toLocation); if (entrancePortal == null || !entrancePortal.getOptions().isBungee() || - entrancePortal.getGate().getPortalOpenBlock() != Material.END_PORTAL) { + !MaterialHelper.specifiersToMaterials( + entrancePortal.getGate().getPortalOpenMaterials()).contains(Material.END_PORTAL)) { return false; } } diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalActivator.java b/src/main/java/net/knarcraft/stargate/portal/PortalActivator.java index 55878f8..53f0ea0 100644 --- a/src/main/java/net/knarcraft/stargate/portal/PortalActivator.java +++ b/src/main/java/net/knarcraft/stargate/portal/PortalActivator.java @@ -3,6 +3,7 @@ package net.knarcraft.stargate.portal; import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.event.StargateActivateEvent; import net.knarcraft.stargate.event.StargateDeactivateEvent; +import net.knarcraft.stargate.utility.ListHelper; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -10,7 +11,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Random; /** * The portal activator activates/de-activates portals and keeps track of a portal's destinations @@ -77,7 +77,7 @@ public class PortalActivator { return null; } //Get one random destination - String destination = destinations.get((new Random()).nextInt(destinations.size())); + String destination = ListHelper.getRandom(destinations); return PortalHandler.getByName(Portal.cleanString(destination), portalNetwork); } else { //Just return the normal fixed destination diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalCreator.java b/src/main/java/net/knarcraft/stargate/portal/PortalCreator.java index b84489e..9668ba8 100644 --- a/src/main/java/net/knarcraft/stargate/portal/PortalCreator.java +++ b/src/main/java/net/knarcraft/stargate/portal/PortalCreator.java @@ -12,8 +12,11 @@ import net.knarcraft.stargate.portal.property.gate.Gate; import net.knarcraft.stargate.portal.property.gate.GateHandler; import net.knarcraft.stargate.utility.DirectionHelper; import net.knarcraft.stargate.utility.EconomyHelper; +import net.knarcraft.stargate.utility.ListHelper; +import net.knarcraft.stargate.utility.MaterialHelper; import net.knarcraft.stargate.utility.PermissionHelper; import net.knarcraft.stargate.utility.PortalFileHelper; +import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; @@ -303,8 +306,12 @@ public class PortalCreator { } else { //Update the block type for the portal's opening to the closed block as the closed block can be anything, // not just air or water + @NotNull List possibleMaterials = MaterialHelper.specifiersToMaterials( + portal.getGate().getPortalClosedMaterials()).stream().toList(); + Material closedType = ListHelper.getRandom(possibleMaterials); + for (BlockLocation entrance : portal.getStructure().getEntrances()) { - entrance.setType(portal.getGate().getPortalClosedBlock()); + entrance.setType(closedType); } } } diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalHandler.java b/src/main/java/net/knarcraft/stargate/portal/PortalHandler.java index caba7e3..cb0c029 100644 --- a/src/main/java/net/knarcraft/stargate/portal/PortalHandler.java +++ b/src/main/java/net/knarcraft/stargate/portal/PortalHandler.java @@ -8,6 +8,7 @@ import net.knarcraft.stargate.portal.property.PortalOption; import net.knarcraft.stargate.portal.property.PortalStructure; import net.knarcraft.stargate.portal.property.gate.Gate; import net.knarcraft.stargate.portal.property.gate.GateHandler; +import net.knarcraft.stargate.utility.MaterialHelper; import net.knarcraft.stargate.utility.PermissionHelper; import org.bukkit.Location; import org.bukkit.World; @@ -445,8 +446,9 @@ public class PortalHandler { for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) { Block block = portal.getBlockAt(control).getBlock(); //Log control blocks not matching the gate layout - if (!block.getType().equals(portal.getGate().getControlBlock())) { - Stargate.debug("PortalHandler::destroyInvalidPortal", "Control Block Type == " + + if (!MaterialHelper.specifiersToMaterials(portal.getGate().getControlBlockMaterials()).contains( + block.getType())) { + Stargate.debug("PortalHandler::unregisterInvalidPortal", "Control Block Type == " + block.getType().name()); } } diff --git a/src/main/java/net/knarcraft/stargate/portal/PortalOpener.java b/src/main/java/net/knarcraft/stargate/portal/PortalOpener.java index 77d5dcb..ac41741 100644 --- a/src/main/java/net/knarcraft/stargate/portal/PortalOpener.java +++ b/src/main/java/net/knarcraft/stargate/portal/PortalOpener.java @@ -6,6 +6,8 @@ import net.knarcraft.stargate.container.BlockLocation; import net.knarcraft.stargate.event.StargateCloseEvent; import net.knarcraft.stargate.event.StargateOpenEvent; import net.knarcraft.stargate.portal.property.PortalOptions; +import net.knarcraft.stargate.utility.ListHelper; +import net.knarcraft.stargate.utility.MaterialHelper; import org.bukkit.Axis; import org.bukkit.Material; import org.bukkit.block.data.Orientable; @@ -13,6 +15,8 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * The portal opener is responsible for opening and closing a portal */ @@ -87,7 +91,10 @@ public class PortalOpener { } //Get the material to change the opening to - Material openType = portal.getGate().getPortalOpenBlock(); + @NotNull List possibleMaterials = MaterialHelper.specifiersToMaterials( + portal.getGate().getPortalOpenMaterials()).stream().toList(); + Material openType = ListHelper.getRandom(possibleMaterials); + //Adjust orientation if applicable Axis axis = (openType.createBlockData() instanceof Orientable) ? portal.getLocation().getRotationAxis() : null; @@ -168,9 +175,15 @@ public class PortalOpener { } //Close the portal by requesting the opening blocks to change - Material closedType = portal.getGate().getPortalClosedBlock(); + @NotNull List possibleMaterials = MaterialHelper.specifiersToMaterials( + portal.getGate().getPortalClosedMaterials()).stream().toList(); + Material closedType = ListHelper.getRandom(possibleMaterials); + + //Adjust orientation if applicable + Axis axis = (closedType.createBlockData() instanceof Orientable) ? portal.getLocation().getRotationAxis() : null; + for (BlockLocation entrance : portal.getStructure().getEntrances()) { - Stargate.addBlockChangeRequest(new BlockChangeRequest(entrance, closedType, null)); + Stargate.addBlockChangeRequest(new BlockChangeRequest(entrance, closedType, axis)); } //Update the portal state to make it actually closed diff --git a/src/main/java/net/knarcraft/stargate/portal/property/gate/Gate.java b/src/main/java/net/knarcraft/stargate/portal/property/gate/Gate.java index 57c6dd2..efcfc30 100644 --- a/src/main/java/net/knarcraft/stargate/portal/property/gate/Gate.java +++ b/src/main/java/net/knarcraft/stargate/portal/property/gate/Gate.java @@ -1,10 +1,12 @@ package net.knarcraft.stargate.portal.property.gate; import net.knarcraft.stargate.Stargate; +import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier; +import net.knarcraft.stargate.config.material.MaterialSpecifier; import net.knarcraft.stargate.container.BlockLocation; import net.knarcraft.stargate.container.RelativeBlockVector; +import net.knarcraft.stargate.utility.MaterialHelper; import org.bukkit.Material; -import org.bukkit.Tag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -13,6 +15,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -24,13 +27,12 @@ public class Gate { private final String filename; private final GateLayout layout; - private final Map characterMaterialMap; - private final Map> characterTagMap; + private final Map> characterMaterialMap; //Gate materials - private final Material portalOpenBlock; - private final Material portalClosedBlock; - private final Material portalButton; + private final List portalOpenMaterials; + private final List portalClosedMaterials; + private final List portalButtonMaterials; //Economy information private final int useCost; @@ -41,34 +43,33 @@ public class Gate { /** * Instantiates a new gate * - * @param filename

The name of the gate file, including extension

- * @param layout

The gate layout defined in the gate file

- * @param characterMaterialMap

The material types the different layout characters represent

- * @param characterTagMap

The material tag types the different layout characters represent

- * @param portalOpenBlock

The material to set the opening to when the portal is open

- * @param portalClosedBlock

The material to set the opening to when the portal is closed

- * @param portalButton

The material to use for the portal button

- * @param useCost

The cost of using a portal with this gate layout (-1 to disable)

- * @param createCost

The cost of creating a portal with this gate layout (-1 to disable)

- * @param destroyCost

The cost of destroying a portal with this gate layout (-1 to disable)

- * @param toOwner

Whether any payment should go to the owner of the gate, as opposed to just disappearing

+ * @param filename

The name of the gate file, including extension

+ * @param layout

The gate layout defined in the gate file

+ * @param characterMaterialsMap

The material types the different layout characters represent

+ * @param portalOpenMaterials

The material to set the opening to when the portal is open

+ * @param portalClosedMaterials

The material to set the opening to when the portal is closed

+ * @param portalButtonMaterials

The material to use for the portal button

+ * @param useCost

The cost of using a portal with this gate layout (-1 to disable)

+ * @param createCost

The cost of creating a portal with this gate layout (-1 to disable)

+ * @param destroyCost

The cost of destroying a portal with this gate layout (-1 to disable)

+ * @param toOwner

Whether any payment should go to the owner of the gate, as opposed to just disappearing

*/ public Gate(@NotNull String filename, @NotNull GateLayout layout, - @NotNull Map characterMaterialMap, - @NotNull Map> characterTagMap, @NotNull Material portalOpenBlock, - @NotNull Material portalClosedBlock, @NotNull Material portalButton, int useCost, int createCost, - int destroyCost, boolean toOwner) { + @NotNull Map> characterMaterialsMap, + @NotNull List portalOpenMaterials, + @NotNull List portalClosedMaterials, + @NotNull List portalButtonMaterials, int useCost, int createCost, int destroyCost, + boolean toOwner) { this.filename = filename; this.layout = layout; - this.characterMaterialMap = characterMaterialMap; - this.portalOpenBlock = portalOpenBlock; - this.portalClosedBlock = portalClosedBlock; - this.portalButton = portalButton; + this.characterMaterialMap = characterMaterialsMap; + this.portalOpenMaterials = portalOpenMaterials; + this.portalClosedMaterials = portalClosedMaterials; + this.portalButtonMaterials = portalButtonMaterials; this.useCost = useCost; this.createCost = createCost; this.destroyCost = destroyCost; this.toOwner = toOwner; - this.characterTagMap = characterTagMap; } /** @@ -87,7 +88,7 @@ public class Gate { * @return

The character to material map

*/ @NotNull - public Map getCharacterMaterialMap() { + public Map> getCharacterMaterialMap() { return new HashMap<>(characterMaterialMap); } @@ -98,18 +99,7 @@ public class Gate { * @return

True if the material is valid for control blocks

*/ public boolean isValidControlBlock(@NotNull Material material) { - return (getControlBlock() != null) ? getControlBlock().equals(material) : - getControlBlockTag().isTagged(material); - } - - /** - * Gets the material tag used for this gate's control blocks - * - * @return

The material tag type used for control blocks

- */ - @NotNull - public Tag getControlBlockTag() { - return characterTagMap.get(GateHandler.getControlBlockCharacter()); + return getControlBlockMaterials().contains(new BukkitMaterialSpecifier(material)); } /** @@ -117,8 +107,8 @@ public class Gate { * * @return

The material type used for control blocks

*/ - @Nullable - public Material getControlBlock() { + @NotNull + public List getControlBlockMaterials() { return characterMaterialMap.get(GateHandler.getControlBlockCharacter()); } @@ -138,8 +128,8 @@ public class Gate { * @return

The block type to use for the opening when open

*/ @NotNull - public Material getPortalOpenBlock() { - return portalOpenBlock; + public List getPortalOpenMaterials() { + return portalOpenMaterials; } /** @@ -148,8 +138,8 @@ public class Gate { * @return

The block type to use for the opening when closed

*/ @NotNull - public Material getPortalClosedBlock() { - return portalClosedBlock; + public List getPortalClosedMaterials() { + return portalClosedMaterials; } /** @@ -158,8 +148,8 @@ public class Gate { * @return

The material to use for a portal's button if using this gate type

*/ @NotNull - public Material getPortalButton() { - return portalButton; + public List getPortalButtonMaterials() { + return portalButtonMaterials; } /** @@ -236,35 +226,27 @@ public class Gate { * @return

True if all border blocks of the gate match the layout

*/ private boolean verifyGateBorderMatches(@NotNull BlockLocation topLeft, double yaw) { - Map characterMaterialMap = new HashMap<>(this.characterMaterialMap); - Map> characterTagMap = new HashMap<>(this.characterTagMap); + Map> characterMaterialMap = new HashMap<>(this.characterMaterialMap); for (RelativeBlockVector borderVector : layout.getBorder()) { int rowIndex = borderVector.right(); int lineIndex = borderVector.down(); Character key = layout.getLayout()[lineIndex][rowIndex]; - Material materialInLayout = characterMaterialMap.get(key); - Tag tagInLayout = characterTagMap.get(key); + List materialInLayout = characterMaterialMap.get(key); Material materialAtLocation = topLeft.getRelativeLocation(borderVector, yaw).getType(); if (materialInLayout != null) { - if (materialAtLocation != materialInLayout) { + if (!MaterialHelper.specifiersToMaterials(materialInLayout).contains(materialAtLocation)) { Stargate.debug("Gate::Matches", String.format("Block Type Mismatch: %s != %s", materialAtLocation, materialInLayout)); return false; } - } else if (tagInLayout != null) { - if (!tagInLayout.isTagged(materialAtLocation)) { - Stargate.debug("Gate::Matches", String.format("Block Type Mismatch: %s != %s", - materialAtLocation, tagInLayout)); - return false; - } } else { /* This generally should not happen with proper checking, but just in case a material character is not * recognized, but still allowed in previous checks, verify the gate as long as all such instances of * the character correspond to the same material in the physical gate. All subsequent gates will also * need to match the first verified gate. */ - characterMaterialMap.put(key, materialAtLocation); + characterMaterialMap.put(key, List.of(new BukkitMaterialSpecifier(materialAtLocation))); Stargate.debug("Gate::Matches", String.format("Missing layout material in %s. Using %s from the" + " physical portal.", getFilename(), materialAtLocation)); } @@ -291,7 +273,8 @@ public class Gate { continue; } - if (type != portalClosedBlock && type != portalOpenBlock) { + if (!MaterialHelper.specifiersToMaterials(portalClosedMaterials).contains(type) && + !MaterialHelper.specifiersToMaterials(portalOpenMaterials).contains(type)) { Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type); return false; } @@ -311,9 +294,9 @@ public class Gate { BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(new File(gateFolder, filename))); //Save main material names - writeConfig(bufferedWriter, "portal-open", portalOpenBlock.name()); - writeConfig(bufferedWriter, "portal-closed", portalClosedBlock.name()); - writeConfig(bufferedWriter, "button", portalButton.name()); + writeConfig(bufferedWriter, "portal-open", MaterialHelper.specifiersToString(portalOpenMaterials)); + writeConfig(bufferedWriter, "portal-closed", MaterialHelper.specifiersToString(portalClosedMaterials)); + writeConfig(bufferedWriter, "button", MaterialHelper.specifiersToString(portalButtonMaterials)); //Save the values necessary for economy saveEconomyValues(bufferedWriter); @@ -361,7 +344,7 @@ public class Gate { * @throws IOException

If unable to write to the buffered writer

*/ private void saveFrameBlockType(@NotNull BufferedWriter bufferedWriter) throws IOException { - for (Map.Entry entry : this.characterMaterialMap.entrySet()) { + for (Map.Entry> entry : this.characterMaterialMap.entrySet()) { Character key = entry.getKey(); //Skip characters not part of the frame if (key.equals(GateHandler.getAnythingCharacter()) || @@ -369,11 +352,7 @@ public class Gate { key.equals(GateHandler.getExitCharacter())) { continue; } - saveFrameBlockType(key, entry.getValue().toString(), bufferedWriter); - } - for (Map.Entry> entry : this.characterTagMap.entrySet()) { - saveFrameBlockType(entry.getKey(), "#" + entry.getValue().getKey().toString().replaceFirst( - "minecraft:", ""), bufferedWriter); + saveFrameBlockType(key, MaterialHelper.specifiersToString(entry.getValue()), bufferedWriter); } } diff --git a/src/main/java/net/knarcraft/stargate/portal/property/gate/GateHandler.java b/src/main/java/net/knarcraft/stargate/portal/property/gate/GateHandler.java index ebdce4e..7a26ef4 100644 --- a/src/main/java/net/knarcraft/stargate/portal/property/gate/GateHandler.java +++ b/src/main/java/net/knarcraft/stargate/portal/property/gate/GateHandler.java @@ -1,7 +1,8 @@ package net.knarcraft.stargate.portal.property.gate; import net.knarcraft.stargate.Stargate; -import net.knarcraft.stargate.utility.GateReader; +import net.knarcraft.stargate.config.material.BukkitMaterialSpecifier; +import net.knarcraft.stargate.config.material.MaterialSpecifier; import net.knarcraft.stargate.utility.MaterialHelper; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -18,6 +19,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; +import java.util.Set; +import java.util.function.Predicate; import static net.knarcraft.stargate.utility.GateReader.generateLayoutMatrix; import static net.knarcraft.stargate.utility.GateReader.readGateConfig; @@ -94,20 +97,13 @@ public class GateHandler { private static void registerGate(@NotNull Gate gate) { gates.put(gate.getFilename(), gate); - Material blockId = gate.getControlBlock(); - if (blockId != null) { - if (!controlBlocks.containsKey(blockId)) { - controlBlocks.put(blockId, new ArrayList<>()); + Set blockTypes = MaterialHelper.specifiersToMaterials(gate.getControlBlockMaterials()); + for (Material material : blockTypes) { + if (!controlBlocks.containsKey(material)) { + controlBlocks.put(material, new ArrayList<>()); } - controlBlocks.get(blockId).add(gate); - return; + controlBlocks.get(material).add(gate); } - - Tag materialTag = gate.getControlBlockTag(); - if (!controlBlockTags.containsKey(materialTag.getKey().toString())) { - controlBlockTags.put(materialTag.getKey().toString(), new ArrayList<>()); - } - controlBlockTags.get(materialTag.getKey().toString()).add(gate); } /** @@ -138,24 +134,23 @@ public class GateHandler { private static Gate loadGate(@NotNull String fileName, @NotNull String parentFolder, @NotNull Scanner scanner) { List> design = new ArrayList<>(); - Map characterMaterialMap = new HashMap<>(); - Map> characterTagMap = new HashMap<>(); + Map> characterMaterialMap = new HashMap<>(); Map config = new HashMap<>(); //Initialize character to material map - characterMaterialMap.put(ENTRANCE, Material.AIR); - characterMaterialMap.put(EXIT, Material.AIR); - characterMaterialMap.put(ANYTHING, Material.AIR); + characterMaterialMap.put(ENTRANCE, List.of(new BukkitMaterialSpecifier(Material.AIR))); + characterMaterialMap.put(EXIT, List.of(new BukkitMaterialSpecifier(Material.AIR))); + characterMaterialMap.put(ANYTHING, List.of(new BukkitMaterialSpecifier(Material.AIR))); //Read the file into appropriate lists and maps - int columns = readGateFile(scanner, characterMaterialMap, characterTagMap, fileName, design, config); + int columns = readGateFile(scanner, characterMaterialMap, fileName, design, config); if (columns < 0) { return null; } Character[][] layout = generateLayoutMatrix(design, columns); //Create and validate the new gate - Gate gate = createGate(config, fileName, layout, characterMaterialMap, characterTagMap); + Gate gate = createGate(config, fileName, layout, characterMaterialMap); if (gate == null) { return null; } @@ -172,28 +167,26 @@ public class GateHandler { * @param fileName

The name of the saved gate config file

* @param layout

The layout matrix of the new gate

* @param characterMaterialMap

A map between layout characters and the material to use

- * @param materialTagMap

A map between layout characters and the material tags to use

* @return

A new gate, or null if the config is invalid

*/ @Nullable private static Gate createGate(@NotNull Map config, @NotNull String fileName, @NotNull Character[][] layout, - @NotNull Map characterMaterialMap, - @NotNull Map> materialTagMap) { + @NotNull Map> characterMaterialMap) { //Read relevant material types - Material portalOpenBlock = readGateConfig(config, fileName, "portal-open", defaultPortalBlockOpen); - Material portalClosedBlock = readGateConfig(config, fileName, "portal-closed", defaultPortalBlockClosed); - Material portalButton = readGateConfig(config, fileName, "button", defaultButton); + List portalOpenBlock = readGateConfig(config, fileName, "portal-open", defaultPortalBlockOpen); + List portalClosedBlock = readGateConfig(config, fileName, "portal-closed", defaultPortalBlockClosed); + List portalButton = readGateConfig(config, fileName, "button", defaultButton); //Read economy values - int useCost = GateReader.readGateConfig(config, fileName, "usecost"); - int createCost = GateReader.readGateConfig(config, fileName, "createcost"); - int destroyCost = GateReader.readGateConfig(config, fileName, "destroycost"); + int useCost = readGateConfig(config, fileName, "usecost"); + int createCost = readGateConfig(config, fileName, "createcost"); + int destroyCost = readGateConfig(config, fileName, "destroycost"); boolean toOwner = (config.containsKey("toowner") ? Boolean.parseBoolean(config.get("toowner")) : Stargate.getEconomyConfig().sendPaymentToOwner()); //Create the new gate - Gate gate = new Gate(fileName, new GateLayout(layout), characterMaterialMap, materialTagMap, portalOpenBlock, + Gate gate = new Gate(fileName, new GateLayout(layout), characterMaterialMap, portalOpenBlock, portalClosedBlock, portalButton, useCost, createCost, destroyCost, toOwner); if (!validateGate(gate, fileName)) { @@ -217,23 +210,23 @@ public class GateHandler { return false; } - if (!MaterialHelper.isButtonCompatible(gate.getPortalButton())) { + if (checkMaterialPredicateFail(gate.getPortalButtonMaterials(), MaterialHelper::isButtonCompatible)) { Stargate.logSevere(String.format(failString, "Gate button must be a type of button.")); return false; } - if (!gate.getPortalOpenBlock().isBlock()) { + if (checkMaterialPredicateFail(gate.getPortalOpenMaterials(), Material::isBlock)) { Stargate.logSevere(String.format(failString, "Gate open block must be a type of block.")); return false; } - if (!gate.getPortalClosedBlock().isBlock()) { + if (checkMaterialPredicateFail(gate.getPortalClosedMaterials(), Material::isBlock)) { Stargate.logSevere(String.format(failString, "Gate closed block must be a type of block.")); return false; } - for (Material material : gate.getCharacterMaterialMap().values()) { - if (!material.isBlock()) { + for (List materialSpecifiers : gate.getCharacterMaterialMap().values()) { + if (checkMaterialPredicateFail(materialSpecifiers, Material::isBlock)) { Stargate.logSevere(String.format(failString, "Every gate border block must be a type of block.")); return false; } @@ -242,6 +235,25 @@ public class GateHandler { return true; } + /** + * Checks whether a predicate is true for a list of material specifiers + * + * @param materialSpecifiers

The material specifiers to test

+ * @param predicate

The predicate to test

+ * @return

True if the predicate failed for any specified materials

+ */ + private static boolean checkMaterialPredicateFail(@NotNull List materialSpecifiers, + @NotNull Predicate predicate) { + Set closedMaterials = MaterialHelper.specifiersToMaterials(materialSpecifiers); + for (Material material : closedMaterials) { + if (!predicate.test(material)) { + return true; + } + } + + return false; + } + /** * Loads all gates inside the given folder * diff --git a/src/main/java/net/knarcraft/stargate/utility/GateReader.java b/src/main/java/net/knarcraft/stargate/utility/GateReader.java index d8804fb..f57c6c0 100644 --- a/src/main/java/net/knarcraft/stargate/utility/GateReader.java +++ b/src/main/java/net/knarcraft/stargate/utility/GateReader.java @@ -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

The scanner to read from

* @param characterMaterialMap

The map of characters to store valid symbols in

- * @param materialTagMap

The map of characters to store valid tag symbols in

* @param fileName

The filename of the loaded gate config file

* @param design

The list to store the loaded design/layout to

* @param config

The map of config values to store to

* @return

The column count/width of the loaded gate

*/ - public static int readGateFile(@NotNull Scanner scanner, @NotNull Map characterMaterialMap, - @NotNull Map> materialTagMap, @NotNull String fileName, - @NotNull List> design, Map config) { + public static int readGateFile(@NotNull Scanner scanner, + @NotNull Map> characterMaterialMap, + @NotNull String fileName, @NotNull List> design, + @NotNull Map 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

The line to read

* @param maxColumns

The current max columns value of the design

* @param characterMaterialMap

The map between characters and the corresponding materials to use

- * @param materialTagMap

The map between characters and the corresponding material tags to use

* @param fileName

The filename of the loaded gate config file

* @param design

The two-dimensional list to store the loaded design to

* @return

The new max columns value of the design

*/ private static int readGateDesignLine(@NotNull String line, int maxColumns, - @NotNull Map characterMaterialMap, - @NotNull Map> materialTagMap, + @NotNull Map> characterMaterialMap, @NotNull String fileName, @NotNull List> design) { List 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

The line to read

* @param characterMaterialMap

The character to material map to store to

- * @param materialTagMap

The character to material tag map to store to

* @param config

The config value map to store to

* @throws Exception

If an invalid material is encountered

*/ - private static void readGateConfigValue(@NotNull String line, @NotNull Map characterMaterialMap, - @NotNull Map> materialTagMap, + private static void readGateConfigValue(@NotNull String line, + @NotNull Map> characterMaterialMap, @NotNull Map 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 tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft(tagString.toLowerCase()), - Material.class); - if (tag != null) { - materialTagMap.put(symbol, tag); - return; - } + List 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

The material specified in the config, or the default material if it could not be read

*/ @NotNull - public static Material readGateConfig(@NotNull Map config, @NotNull String fileName, - @NotNull String key, @NotNull Material defaultMaterial) { + public static List readGateConfig(@NotNull Map 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 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)); } /** diff --git a/src/main/java/net/knarcraft/stargate/utility/ListHelper.java b/src/main/java/net/knarcraft/stargate/utility/ListHelper.java new file mode 100644 index 0000000..fa82656 --- /dev/null +++ b/src/main/java/net/knarcraft/stargate/utility/ListHelper.java @@ -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

The list to get an item from

+ * @param

The type of item the list contains

+ * @return

A random item

+ */ + public static T getRandom(List list) { + return list.get(random.nextInt(list.size())); + } + +} diff --git a/src/main/java/net/knarcraft/stargate/utility/MaterialHelper.java b/src/main/java/net/knarcraft/stargate/utility/MaterialHelper.java index 466e844..2e310bc 100644 --- a/src/main/java/net/knarcraft/stargate/utility/MaterialHelper.java +++ b/src/main/java/net/knarcraft/stargate/utility/MaterialHelper.java @@ -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 specifiers) { + List 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

The material specifiers to convert

+ * @return

The materials the specifiers represent

+ */ + @NotNull + public static Set specifiersToMaterials(@NotNull List specifiers) { + Set 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

The input string to parse

+ * @return

All material specifiers found

+ */ + @NotNull + public static List parseTagsAndMaterials(@NotNull String input) { + List 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 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; + } + } diff --git a/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java b/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java index 2416e98..dcef0e0 100644 --- a/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java +++ b/src/main/java/net/knarcraft/stargate/utility/PermissionHelper.java @@ -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; } diff --git a/src/main/java/net/knarcraft/stargate/utility/PortalFileHelper.java b/src/main/java/net/knarcraft/stargate/utility/PortalFileHelper.java index 33ca2d5..98ff915 100644 --- a/src/main/java/net/knarcraft/stargate/utility/PortalFileHelper.java +++ b/src/main/java/net/knarcraft/stargate/utility/PortalFileHelper.java @@ -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 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); }