Various improvements to the interface

More time units are supported, which makes long temporary durations more readable
Makes right-clicking a sign display a full description, and requires sneaking while clicking to perform the transaction
Makes signs only be destroyed when sneaking
Improves sign protection a bit, but improvements are required
Prevents a creative player breaking a sign from also interacting with it
This commit is contained in:
Kristian Knarvik 2022-01-17 18:42:15 +01:00
parent bec45ae968
commit 23c023a027
8 changed files with 228 additions and 23 deletions

View File

@ -33,6 +33,9 @@ import java.util.Queue;
import java.util.UUID;
import java.util.stream.Stream;
/**
* The main permissionsigns class
*/
public final class PermissionSigns extends JavaPlugin {
private static final Queue<SignCreationRequest> signCreationRequests = new PriorityQueue<>();
@ -131,6 +134,7 @@ public final class PermissionSigns extends JavaPlugin {
PluginDescriptionFile pluginDescriptionFile = this.getDescription();
pluginVersion = pluginDescriptionFile.getVersion();
//TODO: Allow for custom language files. Perhaps just look for strings.yml in the folder
//TODO: Display a notice in the console if a new version is available
FileConfiguration config = this.getConfig();
config.options().copyDefaults(true);

View File

@ -148,11 +148,34 @@ public class PermissionSign {
*
* @return <p>The string used for displaying this sign's duration</p>
*/
private String getDurationString() {
public String getDurationString() {
if (duration == 0) {
return Translator.getTranslatedMessage(TranslatableMessage.SIGN_PERMANENT);
} else {
return duration + " " + Translator.getTranslatedMessage(TranslatableMessage.SIGN_TIME_UNIT);
double minute = 60;
double hour = minute * 60;
double day = hour * 24;
if (duration / day >= 1) {
double days = round(duration / day);
if (days == 1) {
return (int) days + " " + Translator.getTranslatedMessage(TranslatableMessage.UNIT_DAY);
} else {
return days + " " + Translator.getTranslatedMessage(TranslatableMessage.UNIT_DAYS);
}
} else if (duration / hour >= 1) {
double hours = round(duration / hour);
if (hours == 1) {
return (int) hours + " " + Translator.getTranslatedMessage(TranslatableMessage.UNIT_HOUR);
} else {
return hours + " " + Translator.getTranslatedMessage(TranslatableMessage.UNIT_HOURS);
}
} else {
if (duration == 1) {
return duration + " " + Translator.getTranslatedMessage(TranslatableMessage.UNIT_SECOND);
} else {
return duration + " " + Translator.getTranslatedMessage(TranslatableMessage.UNIT_SECONDS);
}
}
}
}
@ -161,7 +184,7 @@ public class PermissionSign {
*
* @return <p>The string used for displaying this sign's cost</p>
*/
private String getCostString() {
public String getCostString() {
if (cost == 0) {
return Translator.getTranslatedMessage(TranslatableMessage.SIGN_COST_FREE);
} else {
@ -170,4 +193,14 @@ public class PermissionSign {
}
}
/**
* Rounds a number to its last two digits
*
* @param number <p>The number to round</p>
* @return <p>The rounded number</p>
*/
private double round(double number) {
return Math.round(number * 100.0) / 100.0;
}
}

View File

@ -16,9 +16,44 @@ public enum TranslatableMessage {
SIGN_PREFIX,
/**
* The text to display as a permission sign's duration time unit
* The text to display for 1 second
*/
SIGN_TIME_UNIT,
UNIT_SECOND,
/**
* The text to display for a number of seconds
*/
UNIT_SECONDS,
/**
* The text to display for 1 minute
*/
UNIT_MINUTE,
/**
* The text to display for a number of minutes
*/
UNIT_MINUTES,
/**
* The text to display for 1 hour
*/
UNIT_HOUR,
/**
* The text to display for a number of hours
*/
UNIT_HOURS,
/**
* The text to display for 1 day
*/
UNIT_DAY,
/**
* The text to display for a number of days
*/
UNIT_DAYS,
/**
* The text to display on a permission sign that is entirely free
@ -50,6 +85,11 @@ public enum TranslatableMessage {
*/
PERMISSION_SIGN_DESTROY_DENY,
/**
* The message to display to a non-sneaking player trying to destroy a permissions sign
*/
PERMISSION_SIGN_DESTROY_SNEAK,
/**
* The error message to display if the user has not provided necessary information to create a permission sign
*/
@ -113,6 +153,11 @@ public enum TranslatableMessage {
/**
* The message to display when a reload action has been successfully performed
*/
RELOAD_SUCCESSFUL
RELOAD_SUCCESSFUL,
/**
* The text describing all properties of a permission sign
*/
SIGN_INFO
}

View File

@ -6,7 +6,6 @@ import org.bukkit.configuration.file.YamlConfiguration;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
@ -57,7 +56,7 @@ public final class Translator {
BufferedReader reader;
try {
reader = FileHelper.getBufferedReaderForInternalFile("/strings.yml");
} catch (FileNotFoundException | UnsupportedEncodingException e) {
} catch (FileNotFoundException e) {
PermissionSigns.getInstance().getLogger().log(Level.SEVERE, "Unable to load translated messages");
return null;
}

View File

@ -6,12 +6,19 @@ import net.knarcraft.permissionsigns.manager.SignManager;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import java.util.ArrayList;
import java.util.List;
/**
* A listener for relevant block events such as signs being broken
*/
public class BlockListener implements Listener {
@EventHandler
@ -23,11 +30,60 @@ public class BlockListener implements Listener {
return;
}
//if (block instanceof FallingBlock) {
//TODO: Search recursively upwards (and downwards if pointed dripstone) until a non-falling block is found.
// If upwards, check the non-falling block, but not downwards. If player does not have the create permission,
// just stop and cancel once a permission sign is found. If it has, all affected signs need to be
// de-registered. Must account for stacks of sand and signs on top of each-other. So if a normal sign is
// found, the recursion upward must also happen.
//}
Material material = block.getBlockData().getMaterial();
if (!Tag.SIGNS.isTagged(material) && !Tag.WALL_SIGNS.isTagged(material)) {
return;
if (Tag.SIGNS.isTagged(material) || Tag.WALL_SIGNS.isTagged(material)) {
checkIfBlockIsPermissionSign(block, player, event);
if (event.isCancelled()) {
return;
}
}
Block relativeBlock = block.getRelative(BlockFace.UP);
if (Tag.SIGNS.isTagged(relativeBlock.getBlockData().getMaterial())) {
checkIfBlockIsPermissionSign(relativeBlock, player, event);
if (event.isCancelled()) {
return;
}
}
if (block.getBlockData().getMaterial() == Material.POINTED_DRIPSTONE) {
relativeBlock = block.getRelative(BlockFace.DOWN);
if (Tag.WALL_SIGNS.isTagged(relativeBlock.getBlockData().getMaterial())) {
checkIfBlockIsPermissionSign(relativeBlock, player, event);
if (event.isCancelled()) {
return;
}
}
}
for (BlockFace blockFace : getRelevantBlockFaces()) {
relativeBlock = block.getRelative(blockFace);
if (Tag.WALL_SIGNS.isTagged(relativeBlock.getBlockData().getMaterial())) {
checkIfBlockIsPermissionSign(relativeBlock, player, event);
if (event.isCancelled()) {
return;
}
}
}
//TODO: Need to protect against other things that might damage the sign, such as explosions
}
/**
* Check if the given block is a permission sign, and perform necessary tasks if it is
*
* @param block <p>The block to check</p>
* @param player <p>The player that caused the event</p>
* @param event <p>The triggered block break event</p>
*/
private void checkIfBlockIsPermissionSign(Block block, Player player, BlockBreakEvent event) {
Sign sign = (Sign) block.getState();
boolean registered = SignManager.getSign(sign.getLocation()) != null;
if (!registered) {
@ -37,12 +93,28 @@ public class BlockListener implements Listener {
event.setCancelled(true);
player.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.PERMISSION_SIGN_DESTROY_DENY));
} else {
if (!player.isSneaking()) {
event.setCancelled(true);
player.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.PERMISSION_SIGN_DESTROY_SNEAK));
return;
}
SignManager.removeSign(sign.getLocation());
player.sendMessage(StringFormatter.getTranslatedInfoMessage(TranslatableMessage.PERMISSION_SIGN_REMOVED));
}
}
//TODO: Need to protect against other things that might damage the sign, such as explosions
//TODO: Only allow the sign to be broken if shift-clicking?
/**
* Gets the relevant block faces for checking around a block
*
* @return <p>The relevant block faces for checking around a block</p>
*/
private List<BlockFace> getRelevantBlockFaces() {
List<BlockFace> relevantBlockFaces = new ArrayList<>();
relevantBlockFaces.add(BlockFace.WEST);
relevantBlockFaces.add(BlockFace.EAST);
relevantBlockFaces.add(BlockFace.NORTH);
relevantBlockFaces.add(BlockFace.SOUTH);
return relevantBlockFaces;
}
}

View File

@ -9,6 +9,8 @@ import net.knarcraft.permissionsigns.formatting.Translator;
import net.knarcraft.permissionsigns.manager.EconomyManager;
import net.knarcraft.permissionsigns.manager.PermissionManager;
import net.knarcraft.permissionsigns.manager.SignManager;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
@ -16,9 +18,11 @@ import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
/**
@ -46,7 +50,7 @@ public class SignListener implements Listener {
}
Sign sign = (Sign) block.getState();
handleSignClick(sign, player);
handleSignClick(sign, player, event.getAction());
}
/**
@ -54,19 +58,26 @@ public class SignListener implements Listener {
*
* @param sign <p>The clicked sign</p>
* @param player <p>The player that clicked the sign</p>
* @param action <p>The hand the player used to interact with the sign</p>
*/
private void handleSignClick(Sign sign, Player player) {
private void handleSignClick(Sign sign, Player player, Action action) {
//Check if the sign is a registered permission sign
PermissionSign permissionSign = SignManager.getSign(sign.getLocation());
if (permissionSign != null) {
if (player.getGameMode() == GameMode.CREATIVE && action == Action.LEFT_CLICK_BLOCK) {
return;
}
if (!player.hasPermission("permissionsigns.use")) {
player.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.INTERACT_PERMISSION_MISSING));
return;
}
handlePermissionSignInteract(permissionSign, player);
if (player.isSneaking()) {
handlePermissionSignInteract(permissionSign, player);
} else {
player.sendMessage(getSignInfoText(permissionSign));
}
return;
}
//TODO: Display information about granted permissions when shift-clicking?
//Check if the player has a creation request that can be fulfilled
SignCreationRequest request = PermissionSigns.getSignCreationRequest(player.getUniqueId());
@ -75,6 +86,31 @@ public class SignListener implements Listener {
}
}
/**
* Gets information about a sign
*
* @param sign <p>The sign to get information about</p>
* @return <p>The information to display</p>
*/
private String getSignInfoText(PermissionSign sign) {
String rawSignInfo = Translator.getTranslatedMessage(TranslatableMessage.SIGN_INFO);
StringBuilder permissionString = new StringBuilder();
List<String> permissionNodes = sign.getPermissionNodes();
if (permissionNodes.size() == 1) {
permissionString = new StringBuilder(permissionNodes.get(0));
} else {
for (String permissionNode : permissionNodes) {
permissionString.append("\n" + "&f| &7- ").append(permissionNode);
}
permissionString.append("\n");
}
String replacedString = StringFormatter.replacePlaceholders(rawSignInfo, new String[]{"{Name}", "{Permissions}",
"{Cost}", "{Duration}"}, new String[]{sign.getName(), permissionString.toString(), sign.getCostString(),
sign.getDurationString()});
return ChatColor.translateAlternateColorCodes('&', replacedString);
}
/**
* Handles the interaction with a permission sign
*
@ -137,9 +173,8 @@ public class SignListener implements Listener {
String grantedPermissions = permissionsJoiner.toString();
if (permissionSign.getDuration() > 0) {
String successMessage = StringFormatter.getTranslatedInfoMessage(TranslatableMessage.PERMISSIONS_GRANTED);
String timeUnit = Translator.getTranslatedMessage(TranslatableMessage.SIGN_TIME_UNIT);
player.sendMessage(StringFormatter.replacePlaceholders(successMessage, new String[]{"{permissions}", "{time}"},
new String[]{grantedPermissions, permissionSign.getDuration() + " " + timeUnit}));
new String[]{grantedPermissions, permissionSign.getDurationString()}));
} else {
player.sendMessage(StringFormatter.replacePlaceholder(StringFormatter.getTranslatedInfoMessage(
TranslatableMessage.PERMISSIONS_GRANTED_PERMANENTLY), "{permissions}", grantedPermissions));

View File

@ -4,7 +4,6 @@ import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
/**
@ -18,7 +17,7 @@ public class FileHelper {
* @return <p>A buffered read for reading the file</p>
* @throws FileNotFoundException <p>If unable to get an input stream for the given file</p>
*/
public static BufferedReader getBufferedReaderForInternalFile(String file) throws FileNotFoundException, UnsupportedEncodingException {
public static BufferedReader getBufferedReaderForInternalFile(String file) throws FileNotFoundException {
InputStream inputStream = FileHelper.class.getResourceAsStream(file);
if (inputStream == null) {
throw new FileNotFoundException("Unable to read the given file");

View File

@ -2,13 +2,21 @@ en:
PREFIX: "[PermissionSigns]"
SIGN_PREFIX: "&4[PermSign]"
MISSING_CREATION_INFO: "&7You must specify a sign name and a comma-separated list of permissions to create a permission sign"
SIGN_TIME_UNIT: "seconds"
UNIT_SECOND: "second"
UNIT_SECONDS: "seconds"
UNIT_MINUTE: "minute"
UNIT_MINUTES: "minutes"
UNIT_HOUR: "hour"
UNIT_HOURS: "hours"
UNIT_DAY: "day"
UNIT_DAYS: "days"
SIGN_PERMANENT: "Permanent"
SIGN_COST_FREE: "Free"
COST_INVALID_NUMBER: "&7The given cost is not a valid number"
DURATION_INVALID_NUMBER: "&7The given duration is not a valid number"
COMMAND_PLAYER_ONLY: "&7This command is only available to players"
PERMISSION_SIGN_DESTROY_DENY: "&7You do not have permissions to delete permissions signs"
PERMISSION_SIGN_DESTROY_SNEAK: "&7You must be sneaking to remove a permissions sign"
PERMISSION_SIGN_REMOVED: "&7Permissions sign removed"
COMMAND_PERMISSION_DENIED: "&7You do not have necessary permissions to perform this command"
CREATION_REQUEST_CREATED: "&7Permission Sign request created. Right-click an empty sign within 60 seconds to create the new permission sign"
@ -21,17 +29,26 @@ en:
INTERACT_PERMISSION_MISSING: "&7You do not have the necessary permissions required to use permission signs"
CREATION_REQUEST_CANCELLED: "&7Your last permission sign creation request has been cancelled"
RELOAD_SUCCESSFUL: "&7The plugin has been successfully reloaded"
SIGN_INFO: "&f---- &4Permission Sign&f ----\n&f| &bName: &7{Name}\n&f| &bPermission(s): &7{Permissions}\n&f| &bCost: &7{Cost}\n&f| &bDuration: &7{Duration}\n&f| &7Right-click the sign while sneaking to buy the permission(s)\n&f-------------------------"
nb-no:
PREFIX: "[Tilgangsrettighetsskilt]"
SIGN_PREFIX: "&4[PermSign]"
MISSING_CREATION_INFO: "&7Du må spesifisere et skiltnavn og en komma-separert liste av tilgangsrettigheter for å opprette et tilgangsrettighetsskilt"
SIGN_TIME_UNIT: "sekunder"
UNIT_SECOND: "sekund"
UNIT_SECONDS: "sekunder"
UNIT_MINUTE: "minutt"
UNIT_MINUTES: "minutter"
UNIT_HOUR: "time"
UNIT_HOURS: "timer"
UNIT_DAY: "dag"
UNIT_DAYS: "dager"
SIGN_PERMANENT: "Permanent"
SIGN_COST_FREE: "Gratis"
COST_INVALID_NUMBER: "&7Den gitte kostnaden er ikke et gyldig nummer"
DURATION_INVALID_NUMBER: "&7Den gitte varigheten er ikke et gyldig nummer"
COMMAND_PLAYER_ONLY: "&7Denne kommandoen kan bare brukes av spillere"
PERMISSION_SIGN_DESTROY_DENY: "&7Du har ikke tillatelse til å ødelegge tilgangsrettighetsskilter"
PERMISSION_SIGN_DESTROY_SNEAK: "&7Du må snike for å fjerne et tilgangsrettighetsskilt"
PERMISSION_SIGN_REMOVED: "&7Tilgangsrettighetsskilt fjernet"
COMMAND_PERMISSION_DENIED: "&7Du har ikke de nødvendige tilgangsrettighetene for å kjøre denne kommandoen"
CREATION_REQUEST_CREATED: "&7Tilgangsrettighetsskiltsforespørsel opprettet. Høyre-klikk på et tomt skilt innen 60 sekunder for å opprette det nye tilgangsrettighetsskiltet"
@ -44,3 +61,4 @@ nb-no:
INTERACT_PERMISSION_MISSING: "&7Du har ikke de nødvendige tilgangsrettighetene for å bruke tilgangsrettighetsskilt"
CREATION_REQUEST_CANCELLED: "&7Din siste tilgangsrettighetsskiltsforespørsel har blitt kansellert"
RELOAD_SUCCESSFUL: "&7Plugin-modulen har blitt lastet inn på nytt"
SIGN_INFO: "&f---- &4Tilgangsrettighetsskilt&f ----\n&f| &bNavn: &7{Name}\n&f| &bTilgangsrettighet(er): &7{Permissions}\n&f| &bKostnad: &7{Cost}\n&f| &bVarighet: &7{Duration}\n&f| &7Høyreklikk skiltet mens du sniker for å kjøpe tilgangsrettighet(en/ene)\n&f-------------------------"