Add hover/click support for messages #18

- Language files support a way to add hover tooltips, click commands and click insert commands (using FancyMessageFormat created together with PhoenixIV)
- Language variables support arguments
- Help listing (/as help) uses the new hover/click things, more to come
- See #18 for the TODO list
This commit is contained in:
Thijs Wiefferink 2016-03-07 21:09:07 +01:00 committed by Thijs Wiefferink
parent 49b536cab0
commit aaff36b2eb
52 changed files with 3415 additions and 1932 deletions

4
.gitignore vendored
View File

@ -28,4 +28,6 @@ local.properties
.buildpath
# IntelliJ IDEA
*.iml
*.iml
.idea
dependency-reduced-pom.xml

View File

@ -3,8 +3,6 @@ package nl.evolutioncoding.areashop;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import net.milkbowl.vault.economy.Economy;
import nl.evolutioncoding.areashop.Updater.UpdateResult;
import nl.evolutioncoding.areashop.Updater.UpdateType;
import nl.evolutioncoding.areashop.features.DebugFeature;
import nl.evolutioncoding.areashop.features.Feature;
import nl.evolutioncoding.areashop.features.SignDisplayFeature;
@ -12,17 +10,21 @@ import nl.evolutioncoding.areashop.features.WorldGuardRegionFlagsFeature;
import nl.evolutioncoding.areashop.interfaces.AreaShopInterface;
import nl.evolutioncoding.areashop.interfaces.WorldEditInterface;
import nl.evolutioncoding.areashop.interfaces.WorldGuardInterface;
import nl.evolutioncoding.areashop.lib.Metrics;
import nl.evolutioncoding.areashop.lib.Updater;
import nl.evolutioncoding.areashop.lib.Updater.UpdateResult;
import nl.evolutioncoding.areashop.lib.Updater.UpdateType;
import nl.evolutioncoding.areashop.listeners.PlayerLoginLogoutListener;
import nl.evolutioncoding.areashop.listeners.SignBreakListener;
import nl.evolutioncoding.areashop.listeners.SignChangeListener;
import nl.evolutioncoding.areashop.listeners.SignClickListener;
import nl.evolutioncoding.areashop.managers.CommandManager;
import nl.evolutioncoding.areashop.managers.FileManager;
import nl.evolutioncoding.areashop.managers.LanguageManager;
import nl.evolutioncoding.areashop.managers.SignLinkerManager;
import nl.evolutioncoding.areashop.messages.LanguageManager;
import nl.evolutioncoding.areashop.messages.Message;
import nl.evolutioncoding.areashop.regions.GeneralRegion;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
@ -35,8 +37,8 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* Main class for the AreaShop plugin
@ -55,7 +57,7 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface {
private CommandManager commandManager = null;
private SignLinkerManager signLinkerManager = null;
private boolean debug = false;
private String chatprefix = null;
private List<String> chatprefix = null;
private Updater updater = null;
private boolean updateAvailable = false;
private boolean ready = false;
@ -335,7 +337,7 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface {
* Set the chatprefix to use in the chat (loaded from config normally)
* @param chatprefix The string to use in front of chat messages (supports formatting codes like &1)
*/
public void setChatprefix(String chatprefix) {
public void setChatprefix(List<String> chatprefix) {
this.chatprefix = chatprefix;
}
@ -408,7 +410,7 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface {
* Get the current chatPrefix
* @return The current chatPrefix
*/
public String getChatPrefix() {
public List<String> getChatPrefix() {
return chatprefix;
}
@ -549,50 +551,13 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface {
}.runTaskLater(this, 20L);
}
}
/**
* Method to send a message to a CommandSender, using chatprefix if it is a player
* @param target The CommandSender you wan't to send the message to (e.g. a player)
* @param key The key to get the translation
* @param prefix Specify if the message should have a prefix
* @param params The parameters to inject into the message string
*/
public void configurableMessage(Object target, String key, boolean prefix, Object... params) {
if(target == null) {
return;
}
String langString = Utils.applyColors(languageManager.getLang(key, params));
if(langString == null || langString.equals("")) {
// Do nothing, message is not available or disabled
} else {
if(target instanceof Player) {
if(prefix) {
((Player)target).sendMessage(Utils.applyColors(chatprefix)+langString);
} else {
((Player)target).sendMessage(langString);
}
} else if(target instanceof CommandSender) {
if(!getConfig().getBoolean("useColorsInConsole")) {
langString = ChatColor.stripColor(langString);
}
((CommandSender)target).sendMessage(langString);
}
else if(target instanceof Logger) {
if(!getConfig().getBoolean("useColorsInConsole")) {
langString = ChatColor.stripColor(langString);
}
((Logger)target).info(langString);
} else {
langString = ChatColor.stripColor(langString);
this.getLogger().info("Could not send message, target is wrong: " + langString);
}
}
public void messageNoPrefix(Object target, String key, Object... replacements) {
Message.fromKey(key).replacements(replacements).send(target);
}
public void messageNoPrefix(Object target, String key, Object... params) {
configurableMessage(target, key, false, params);
}
public void message(Object target, String key, Object... params) {
configurableMessage(target, key, true, params);
public void message(Object target, String key, Object... replacements) {
Message.fromKey(key).prefix().replacements(replacements).send(target);
}

View File

@ -5,6 +5,7 @@ import com.sk89q.worldedit.bukkit.selections.CuboidSelection;
import com.sk89q.worldedit.bukkit.selections.Selection;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import nl.evolutioncoding.areashop.messages.Message;
import nl.evolutioncoding.areashop.regions.BuyRegion;
import nl.evolutioncoding.areashop.regions.GeneralRegion;
import nl.evolutioncoding.areashop.regions.GeneralRegion.RegionType;
@ -157,6 +158,47 @@ public class Utils {
return milliseconds/50;
}
/**
* Convert milliseconds to a human readable format
* @param milliseconds The amount of milliseconds to convert
* @return A formatted string based on the language file
*/
public static String millisToHumanFormat(long milliseconds) {
long timeLeft = milliseconds+500;
// To seconds
timeLeft = timeLeft/1000;
if(timeLeft <= 0) {
return Message.fromKey("timeleft-ended").getPlain();
} else if(timeLeft == 1) {
return Message.fromKey("timeleft-second").replacements(timeLeft).getPlain();
} else if(timeLeft <= 120) {
return Message.fromKey("timeleft-seconds").replacements(timeLeft).getPlain();
}
// To minutes
timeLeft = timeLeft/60;
if(timeLeft <= 120) {
return Message.fromKey("timeleft-minutes").replacements(timeLeft).getPlain();
}
// To hours
timeLeft = timeLeft/60;
if(timeLeft <= 48) {
return Message.fromKey("timeleft-hours").replacements(timeLeft).getPlain();
}
// To days
timeLeft = timeLeft/24;
if(timeLeft <= 60) {
return Message.fromKey("timeleft-days").replacements(timeLeft).getPlain();
}
// To months
timeLeft = timeLeft/30;
if(timeLeft <= 24) {
return Message.fromKey("timeleft-months").replacements(timeLeft).getPlain();
}
// To years
timeLeft = timeLeft/12;
return Message.fromKey("timeleft-years").replacements(timeLeft).getPlain();
}
private static final BlockFace[] facings = {BlockFace.NORTH, BlockFace.NORTH_EAST, BlockFace.EAST, BlockFace.SOUTH_EAST, BlockFace.SOUTH, BlockFace.SOUTH_WEST, BlockFace.WEST, BlockFace.NORTH_WEST};
/**

View File

@ -32,7 +32,7 @@ public class AddCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.createrent") || target.hasPermission("areashop.createbuy")) {
return plugin.getLanguageManager().getLang("help-add");
return "help-add";
}
return null;
}

View File

@ -27,9 +27,9 @@ public class AddfriendCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.addfriendall")) {
return plugin.getLanguageManager().getLang("help-addFriendAll");
return "help-addFriendAll";
} else if(target.hasPermission("areashop.addfriend")) {
return plugin.getLanguageManager().getLang("help-addFriend");
return "help-addFriend";
}
return null;
}

View File

@ -29,7 +29,7 @@ public class AddsignCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.addsign")) {
return plugin.getLanguageManager().getLang("help-addsign");
return "help-addsign";
}
return null;
}

View File

@ -23,7 +23,7 @@ public class BuyCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.buy")) {
return plugin.getLanguageManager().getLang("help-buy");
return "help-buy";
}
return null;
}

View File

@ -49,9 +49,9 @@ public abstract class CommandAreaShop {
public abstract String getCommandStart();
/**
* Returns the correct help string for the reciever
* Returns the correct help string key to be used on the help page
* @param target The CommandSender that the help message is for
* @return The help message according to the permissions of the reciever
* @return The help message key according to the permissions of the reciever
*/
public abstract String getHelp(CommandSender target);

View File

@ -28,7 +28,7 @@ public class DelCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.destroyrent") || target.hasPermission("areashop.destroybuy")) {
return plugin.getLanguageManager().getLang("help-del");
return "help-del";
}
return null;
}

View File

@ -27,9 +27,9 @@ public class DelfriendCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.delfriendall")) {
return plugin.getLanguageManager().getLang("help-delFriendAll");
return "help-delFriendAll";
} else if(target.hasPermission("areashop.delfriend")) {
return plugin.getLanguageManager().getLang("help-delFriend");
return "help-delFriend";
}
return null;
}

View File

@ -25,7 +25,7 @@ public class DelsignCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.delsign")) {
return plugin.getLanguageManager().getLang("help-delsign");
return "help-delsign";
}
return null;
}

View File

@ -2,6 +2,7 @@ package nl.evolutioncoding.areashop.commands;
import nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.Utils;
import nl.evolutioncoding.areashop.messages.Message;
import nl.evolutioncoding.areashop.regions.BuyRegion;
import nl.evolutioncoding.areashop.regions.RegionGroup;
import nl.evolutioncoding.areashop.regions.RentRegion;
@ -26,7 +27,7 @@ public class FindCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.find")) {
return plugin.getLanguageManager().getLang("help-find");
return "help-find";
}
return null;
}
@ -84,9 +85,9 @@ public class FindCommand extends CommandAreaShop {
if(!results.isEmpty()) {
// Draw a random one
BuyRegion region = results.get(new Random().nextInt(results.size()));
String onlyInGroup = "";
Message onlyInGroup = Message.none();
if(group != null) {
onlyInGroup = plugin.getLanguageManager().getLang("find-onlyInGroup", args[3]);
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
if(maxPriceSet) {
plugin.message(player, "find-successMax", "buy", Utils.formatCurrency(maxPrice), onlyInGroup, region);
@ -95,9 +96,9 @@ public class FindCommand extends CommandAreaShop {
}
region.teleportPlayer(player, region.getBooleanSetting("general.findTeleportToSign"), false);
} else {
String onlyInGroup = "";
Message onlyInGroup = Message.none();
if(group != null) {
onlyInGroup = plugin.getLanguageManager().getLang("find-onlyInGroup", args[3]);
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
if(maxPriceSet) {
plugin.message(player, "find-noneFoundMax", "buy", Utils.formatCurrency(maxPrice), onlyInGroup);
@ -118,9 +119,9 @@ public class FindCommand extends CommandAreaShop {
if(!results.isEmpty()) {
// Draw a random one
RentRegion region = results.get(new Random().nextInt(results.size()));
String onlyInGroup = "";
Message onlyInGroup = Message.none();
if(group != null) {
onlyInGroup = plugin.getLanguageManager().getLang("find-onlyInGroup", args[3]);
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
if(maxPriceSet) {
plugin.message(player, "find-successMax", "rent", Utils.formatCurrency(maxPrice), onlyInGroup, region);
@ -129,9 +130,9 @@ public class FindCommand extends CommandAreaShop {
}
region.teleportPlayer(player, region.getBooleanSetting("general.findTeleportToSign"), false);
} else {
String onlyInGroup = "";
Message onlyInGroup = Message.none();
if(group != null) {
onlyInGroup = plugin.getLanguageManager().getLang("find-onlyInGroup", args[3]);
onlyInGroup = Message.fromKey("find-onlyInGroup").replacements(args[3]);
}
if(maxPriceSet) {
plugin.message(player, "find-noneFoundMax", "rent", Utils.formatCurrency(maxPrice), onlyInGroup);

View File

@ -25,7 +25,7 @@ public class GroupaddCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.groupadd")) {
return plugin.getLanguageManager().getLang("help-groupadd");
return "help-groupadd";
}
return null;
}

View File

@ -25,7 +25,7 @@ public class GroupdelCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.groupdel")) {
return plugin.getLanguageManager().getLang("help-groupdel");
return "help-groupdel";
}
return null;
}

View File

@ -22,7 +22,7 @@ public class GroupinfoCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.groupinfo")) {
return plugin.getLanguageManager().getLang("help-groupinfo");
return "help-groupinfo";
}
return null;
}

View File

@ -21,7 +21,7 @@ public class GrouplistCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.grouplist")) {
return plugin.getLanguageManager().getLang("help-grouplist");
return "help-grouplist";
}
return null;
}

View File

@ -17,7 +17,7 @@ public class HelpCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.help")) {
return plugin.getLanguageManager().getLang("help-help");
return "help-help";
}
return null;
}

View File

@ -2,6 +2,7 @@ package nl.evolutioncoding.areashop.commands;
import nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.Utils;
import nl.evolutioncoding.areashop.messages.Message;
import nl.evolutioncoding.areashop.regions.BuyRegion;
import nl.evolutioncoding.areashop.regions.GeneralRegion;
import nl.evolutioncoding.areashop.regions.RegionGroup;
@ -27,7 +28,7 @@ public class InfoCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.info")) {
return plugin.getLanguageManager().getLang("help-info");
return "help-info";
}
return null;
}
@ -225,7 +226,7 @@ public class InfoCommand extends CommandAreaShop {
Location teleport = rent.getTeleportLocation();
if(teleport == null) {
if(rent.isRented()) {
plugin.messageNoPrefix(sender, "info-regionNoTeleport", rent, plugin.getLanguageManager().getLang("info-regionTeleportHint", rent));
plugin.messageNoPrefix(sender, "info-regionNoTeleport", rent, Message.fromKey("info-regionTeleportHint").replacements(rent));
} else {
plugin.messageNoPrefix(sender, "info-regionNoTeleport", rent, "");
}
@ -235,17 +236,17 @@ public class InfoCommand extends CommandAreaShop {
}
List<String> signLocations = new ArrayList<>();
for(Location location : rent.getSignLocations()) {
signLocations.add(plugin.getLanguageManager().getLang("info-regionSignLocation", location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()));
signLocations.add(Message.fromKey("info-regionSignLocation").replacements(location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()).getPlain());
}
if(!signLocations.isEmpty()) {
plugin.messageNoPrefix(sender, "info-regionSigns", Utils.createCommaSeparatedList(signLocations));
plugin.messageNoPrefix(sender, "info-regionSigns", signLocations.toArray());
}
if(sender.hasPermission("areashop.groupinfo") && !rent.getGroupNames().isEmpty()) {
plugin.messageNoPrefix(sender, "info-regionGroups", Utils.createCommaSeparatedList(rent.getGroupNames()));
}
if(rent.isRestoreEnabled()) {
if(sender.hasPermission("areashop.setrestore")) {
plugin.messageNoPrefix(sender, "info-regionRestoringRent", rent, plugin.getLanguageManager().getLang("info-regionRestoringProfile", rent.getRestoreProfile()));
plugin.messageNoPrefix(sender, "info-regionRestoringRent", rent, Message.fromKey("info-regionRestoringProfile").replacements(rent.getRestoreProfile()));
} else {
plugin.messageNoPrefix(sender, "info-regionRestoringRent", rent, "");
}
@ -284,7 +285,7 @@ public class InfoCommand extends CommandAreaShop {
Location teleport = buy.getTeleportLocation();
if(teleport == null) {
if(buy.isSold()) {
plugin.messageNoPrefix(sender, "info-regionNoTeleport", buy, plugin.getLanguageManager().getLang("info-regionTeleportHint", buy));
plugin.messageNoPrefix(sender, "info-regionNoTeleport", buy, Message.fromKey("info-regionTeleportHint").replacements(buy));
} else {
plugin.messageNoPrefix(sender, "info-regionNoTeleport", buy, "");
}
@ -294,17 +295,17 @@ public class InfoCommand extends CommandAreaShop {
}
List<String> signLocations = new ArrayList<>();
for(Location location : buy.getSignLocations()) {
signLocations.add(plugin.getLanguageManager().getLang("info-regionSignLocation", location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()));
signLocations.add(Message.fromKey("info-regionSignLocation").replacements(location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()).getPlain());
}
if(!signLocations.isEmpty()) {
plugin.messageNoPrefix(sender, "info-regionSigns", Utils.createCommaSeparatedList(signLocations));
plugin.messageNoPrefix(sender, "info-regionSigns", signLocations.toArray());
}
if(sender.hasPermission("areashop.groupinfo") && !buy.getGroupNames().isEmpty()) {
plugin.messageNoPrefix(sender, "info-regionGroups", Utils.createCommaSeparatedList(buy.getGroupNames()));
}
if(buy.isRestoreEnabled()) {
if(sender.hasPermission("areashop.setrestore")) {
plugin.messageNoPrefix(sender, "info-regionRestoringBuy", buy, plugin.getLanguageManager().getLang("info-regionRestoringProfile", buy.getRestoreProfile()));
plugin.messageNoPrefix(sender, "info-regionRestoringBuy", buy, Message.fromKey("info-regionRestoringProfile").replacements(buy.getRestoreProfile()));
} else {
plugin.messageNoPrefix(sender, "info-regionRestoringBuy", buy, "");
}

View File

@ -23,7 +23,7 @@ public class LinksignsCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.linksigns")) {
return plugin.getLanguageManager().getLang("help-linksigns");
return "help-linksigns";
}
return null;
}

View File

@ -24,7 +24,7 @@ public class MeCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.me")) {
return plugin.getLanguageManager().getLang("help-me");
return "help-me";
}
return null;
}

View File

@ -17,7 +17,7 @@ public class ReloadCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.reload")) {
return plugin.getLanguageManager().getLang("help-reload");
return "help-reload";
}
return null;
}

View File

@ -23,7 +23,7 @@ public class RentCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.rent")) {
return plugin.getLanguageManager().getLang("help-rent");
return "help-rent";
}
return null;
}

View File

@ -23,9 +23,9 @@ public class ResellCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.resellall")) {
return plugin.getLanguageManager().getLang("help-resellAll");
return "help-resellAll";
} else if(target.hasPermission("areashop.resell")) {
return plugin.getLanguageManager().getLang("help-resell");
return "help-resell";
}
return null;
}

View File

@ -24,7 +24,7 @@ public class SchematiceventCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.schematicevents")) {
return plugin.getLanguageManager().getLang("help-schemevent");
return "help-schemevent";
}
return null;
}

View File

@ -23,9 +23,9 @@ public class SellCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.sell")) {
return plugin.getLanguageManager().getLang("help-sell");
return "help-sell";
} else if(target.hasPermission("areashop.sellown")) {
return plugin.getLanguageManager().getLang("help-sellOwn");
return "help-sellOwn";
}
return null;
}

View File

@ -23,7 +23,7 @@ public class SetdurationCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setduration")) {
return plugin.getLanguageManager().getLang("help-setduration");
return "help-setduration";
}
return null;
}

View File

@ -25,7 +25,7 @@ public class SetlandlordCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setlandlord")) {
return plugin.getLanguageManager().getLang("help-setlandlord");
return "help-setlandlord";
}
return null;
}

View File

@ -29,7 +29,7 @@ public class SetownerCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setownerrent") || target.hasPermission("areashop.setownerbuy")) {
return plugin.getLanguageManager().getLang("help-setowner");
return "help-setowner";
}
return null;
}

View File

@ -25,7 +25,7 @@ public class SetpriceCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setprice")) {
return plugin.getLanguageManager().getLang("help-setprice");
return "help-setprice";
}
return null;
}

View File

@ -21,7 +21,7 @@ public class SetrestoreCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.setrestore")) {
return plugin.getLanguageManager().getLang("help-setrestore");
return "help-setrestore";
}
return null;
}

View File

@ -26,9 +26,9 @@ public class SetteleportCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.teleportall")) {
return plugin.getLanguageManager().getLang("help-setteleportAll");
return "help-setteleportAll";
} else if(target.hasPermission("areashop.teleport")) {
return plugin.getLanguageManager().getLang("help-setteleport");
return "help-setteleport";
}
return null;
}

View File

@ -6,6 +6,7 @@ import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
import nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.Utils;
import nl.evolutioncoding.areashop.messages.Message;
import nl.evolutioncoding.areashop.regions.BuyRegion;
import nl.evolutioncoding.areashop.regions.GeneralRegion.RegionEvent;
import nl.evolutioncoding.areashop.regions.RegionGroup;
@ -33,7 +34,7 @@ public class StackCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.stack")) {
return plugin.getLanguageManager().getLang("help-stack");
return "help-stack";
}
return null;
}
@ -124,9 +125,9 @@ public class StackCommand extends CommandAreaShop {
} else {
type = "buy";
}
String groupsMessage = "";
Message groupsMessage = Message.none();
if(group != null) {
groupsMessage = plugin.getLanguageManager().getLang("stack-addToGroup", group.getName());
groupsMessage = Message.fromKey("stack-addToGroup").replacements(group.getName());
}
plugin.message(player, "stack-accepted", amount, type, gap, namePrefix, groupsMessage);
plugin.message(player, "stack-addStart", amount, regionsPerTick*20);

View File

@ -23,9 +23,9 @@ public class StopresellCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.stopresellall")) {
return plugin.getLanguageManager().getLang("help-stopResellAll");
return "help-stopResellAll";
} else if(target.hasPermission("areashop.stopresell")) {
return plugin.getLanguageManager().getLang("help-stopResell");
return "help-stopResell";
}
return null;
}

View File

@ -22,9 +22,9 @@ public class TeleportCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.teleportall")) {
return plugin.getLanguageManager().getLang("help-teleportAll");
return "help-teleportAll";
} else if(target.hasPermission("areashop.teleport")) {
return plugin.getLanguageManager().getLang("help-teleport");
return "help-teleport";
}
return null;
}

View File

@ -23,9 +23,9 @@ public class UnrentCommand extends CommandAreaShop {
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.unrent")) {
return plugin.getLanguageManager().getLang("help-unrent");
return "help-unrent";
} else if(target.hasPermission("areashop.unrentown")) {
return plugin.getLanguageManager().getLang("help-unrentOwn");
return "help-unrentOwn";
}
return null;
}

View File

@ -8,6 +8,7 @@ import com.sk89q.worldguard.protection.flags.RegionGroupFlag;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.events.notify.RegionUpdateEvent;
import nl.evolutioncoding.areashop.messages.Message;
import nl.evolutioncoding.areashop.regions.GeneralRegion;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventHandler;
@ -49,8 +50,7 @@ public class WorldGuardRegionFlagsFeature extends Feature implements Listener {
}
// Loop through all flags that are set in the config
for(String flagName : flagNames) {
String value = flags.getString(flagName);
value = region.applyAllReplacements(value);
String value = Message.fromString(flags.getString(flagName)).replacements(region).getPlain();
// In the config normal Bukkit color codes are used, those only need to be translated on 5.X WorldGuard versions
if(plugin.getWorldGuard().getDescription().getVersion().startsWith("5.")) {
value = translateBukkitToWorldGuardColors(value);

View File

@ -1,14 +1,11 @@
package nl.evolutioncoding.areashop.managers;
import nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.Utils;
import nl.evolutioncoding.areashop.commands.*;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
@ -78,22 +75,17 @@ public class CommandManager implements CommandExecutor, TabCompleter {
public void showHelp(CommandSender target) {
// Add all messages to a list
ArrayList<String> messages = new ArrayList<>();
messages.add(plugin.getConfig().getString("chatPrefix") + plugin.getLanguageManager().getLang("help-header"));
messages.add(plugin.getConfig().getString("chatPrefix") + plugin.getLanguageManager().getLang("help-alias"));
plugin.message(target, "help-header");
plugin.message(target, "help-alias");
for(CommandAreaShop command : commands) {
String help = command.getHelp(target);
if(help != null && help.length() != 0) {
messages.add(help);
}
}
// Send the messages to the target
for(String message : messages) {
if(!plugin.getConfig().getBoolean("useColorsInConsole") && !(target instanceof Player)) {
target.sendMessage(ChatColor.stripColor(Utils.applyColors(message)));
} else {
target.sendMessage(Utils.applyColors(message));
}
plugin.messageNoPrefix(target, message);
}
}

View File

@ -819,7 +819,13 @@ public class FileManager {
config.addDefaults(YamlConfiguration.loadConfiguration(normal));
// Set the debug and chatprefix variables
plugin.setDebug(this.getConfig().getBoolean("debug"));
plugin.setChatprefix(this.getConfig().getString("chatPrefix"));
if(getConfig().isList("chatPrefix")) {
plugin.setChatprefix(getConfig().getStringList("chatPrefix"));
} else {
ArrayList<String> list = new ArrayList<>();
list.add(getConfig().getString("chatPrefix"));
plugin.setChatprefix(list);
}
}
} catch(IOException e) {
plugin.getLogger().warning("Something went wrong while reading the config.yml file: " + configFile.getAbsolutePath());

View File

@ -1,199 +0,0 @@
package nl.evolutioncoding.areashop.managers;
import com.google.common.base.Charsets;
import nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.regions.GeneralRegion;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class LanguageManager {
private AreaShop plugin = null;
private String languages[] = {"EN", "NL", "DE", "CS", "FR", "FI", "PL"};
private HashMap<String, String> currentLanguage, defaultLanguage;
/**
* Constructor
* @param plugin The AreaShop plugin
*/
public LanguageManager(AreaShop plugin) {
this.plugin = plugin;
startup();
}
/**
* Save the default language files and open the current and backup language file
*/
public void startup() {
this.saveDefaults();
this.loadLanguage();
}
/**
* Saves the default language files if not already present
*/
public void saveDefaults() {
// Create the language folder if it not exists
File langFolder;
langFolder = new File(plugin.getDataFolder() + File.separator + AreaShop.languageFolder);
if(!langFolder.exists()) {
if(!langFolder.mkdirs()) {
plugin.getLogger().warning("Could not create language directory: " + langFolder.getAbsolutePath());
return;
}
}
// Create the language files, overwrites if a file already exists
// Overriding is necessary because otherwise with an update the new lang
// files would not be used, when translating your own use another
// file name as the default
File langFile;
for(String language : languages) {
langFile = new File(plugin.getDataFolder() + File.separator + AreaShop.languageFolder + File.separator + language + ".yml");
try(
InputStream input = plugin.getResource(AreaShop.languageFolder + "/" + language + ".yml");
OutputStream output = new FileOutputStream(langFile)
) {
if(input == null) {
plugin.getLogger().warning("Could not save default language to the '" + AreaShop.languageFolder + "' folder: " + language + ".yml");
continue;
}
int read;
byte[] bytes = new byte[1024];
while ((read = input.read(bytes)) != -1) {
output.write(bytes, 0, read);
}
input.close();
output.close();
} catch(IOException e) {
plugin.getLogger().warning("Something went wrong saving a default language file: " + langFile.getPath());
}
}
}
/**
* Loads the current language file specified in the config
*/
public void loadLanguage() {
Map<String, Object> map;
Set<String> set;
YamlConfiguration ymlFile;
// Save the current language file to the HashMap
currentLanguage = new HashMap<>();
File file = new File(plugin.getDataFolder() + File.separator + AreaShop.languageFolder + File.separator + plugin.getConfig().getString("language") + ".yml");
try(
InputStreamReader reader = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)
) {
ymlFile = YamlConfiguration.loadConfiguration(reader);
map = ymlFile.getValues(true);
set = map.keySet();
for(String key : set) {
if(map.get(key) instanceof String) {
currentLanguage.put(key, (String)map.get(key));
}
}
} catch(IOException e) {
plugin.getLogger().warning("Could not load set language file: " + file.getAbsolutePath());
}
// Save the default strings to the HashMap
defaultLanguage = new HashMap<>();
File standard = new File(plugin.getDataFolder() + File.separator + AreaShop.languageFolder + "/" + languages[0]+ ".yml");
try(
InputStreamReader reader = new InputStreamReader(new FileInputStream(standard), Charsets.UTF_8)
) {
ymlFile = YamlConfiguration.loadConfiguration(reader);
map = ymlFile.getValues(true);
set = map.keySet();
for(String key : set) {
if(map.get(key) instanceof String) {
defaultLanguage.put(key, (String)map.get(key));
}
}
} catch(IOException e) {
plugin.getLogger().warning("Could not load default language file: " + file.getAbsolutePath());
}
}
/**
* Function to get the string in the language that has been set
* @param key Key to the language string
* @param params The replacements for the %1% tags
* @return String The language string specified with the key
*/
public String getLang(String key, Object... params) {
String result;
// Get the language string
if(currentLanguage.containsKey(key)) {
result = currentLanguage.get(key);
} else {
result = defaultLanguage.get(key);
}
if(result == null) {
plugin.getLogger().info("Wrong key for getting translation: " + key + ", please contact the author about this");
} else {
// Replace all tags like %0% and if given a GeneralRegion apply all replacements
int number=0;
for(Object param : params) {
if(param != null) {
if(param instanceof GeneralRegion) {
result = ((GeneralRegion)param).applyAllReplacements(result);
} else {
result = result.replace("%" + number + "%", param.toString());
number++;
}
}
}
}
return result;
}
}

View File

@ -0,0 +1,107 @@
package nl.evolutioncoding.areashop.messages;
import org.apache.commons.lang.Validate;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
/**
* Class made by glan3b (Github: https://github.com/glen3b) for the Fanciful project (Github: https://github.com/mkremins/fanciful)
*/
/**
* Represents a wrapper around an array class of an arbitrary reference type,
* which properly implements "value" hash code and equality functions.
* <p>
* This class is intended for use as a key to a map.
* </p>
* @param <E> The type of elements in the array.
* @author Glen Husman
* @see Arrays
*/
public final class ArrayWrapper<E> {
/**
* Creates an array wrapper with some elements.
* @param elements The elements of the array.
*/
public ArrayWrapper(E... elements) {
setArray(elements);
}
private E[] _array;
/**
* Retrieves a reference to the wrapped array instance.
* @return The array wrapped by this instance.
*/
public E[] getArray() {
return _array;
}
/**
* Set this wrapper to wrap a new array instance.
* @param array The new wrapped array.
*/
public void setArray(E[] array) {
Validate.notNull(array, "The array must not be null.");
_array = array;
}
/**
* Determines if this object has a value equivalent to another object.
* @see Arrays#equals(Object[], Object[])
*/
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object other) {
if(!(other instanceof ArrayWrapper)) {
return false;
}
return Arrays.equals(_array, ((ArrayWrapper)other)._array);
}
/**
* Gets the hash code represented by this objects value.
* @return This object's hash code.
* @see Arrays#hashCode(Object[])
*/
@Override
public int hashCode() {
return Arrays.hashCode(_array);
}
/**
* Converts an iterable element collection to an array of elements.
* The iteration order of the specified object will be used as the array element order.
* @param list The iterable of objects which will be converted to an array.
* @param c The type of the elements of the array.
* @return An array of elements in the specified iterable.
*/
@SuppressWarnings("unchecked")
public static <T> T[] toArray(Iterable<? extends T> list, Class<T> c) {
int size = -1;
if(list instanceof Collection<?>) {
@SuppressWarnings("rawtypes")
Collection coll = (Collection)list;
size = coll.size();
}
if(size < 0) {
size = 0;
// Ugly hack: Count it ourselves
for(@SuppressWarnings("unused") T element : list) {
size++;
}
}
T[] result = (T[])Array.newInstance(c, size);
int i = 0;
for(T element : list) { // Assumes iteration order is consistent
result[i++] = element; // Assign array element at index THEN increment counter
}
return result;
}
}

View File

@ -0,0 +1,853 @@
package nl.evolutioncoding.areashop.messages;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* FancyMessageFormat converter, a library that enables to convert
* messages in the FancyMessageFormat to Minecraft's bulky tellraw
* format.
* @author NLThijs48 | http://wiefferink.me
* @author Tobias aka Phoenix | http://www.phoenix-iv.de
*/
public class FancyMessageFormat {
private static final char TAG_BEFORE = '[';
private static final char TAG_AFTER = ']';
private static final char END_TAG_INDICATOR = '/';
/**
* The special character that prefixes all basic chat formatting codes.
*/
private static final char SIMPLE_FORMAT_PREFIX_CHAR = '\u00A7';
/**
* Resets all previous chat colors or formats.
*/
private static final char SIMPLE_FORMAT_RESET_CHAR = 'r';
/**
* Lookup table for all continuous tags (marked by [])
*/
private static final HashMap<String, Tag> BRACKET_TAG_LIST = new HashMap<>();
/**
* Lookup table for all interactive tags
*/
private static final HashMap<String, Tag> INTERACTIVE_TAG_LIST = new HashMap<>();
static {
// Enlist all possible tags
// (They go into a HashMap for lookup purposes)
cacheTags(BRACKET_TAG_LIST, Color.class);
cacheTags(BRACKET_TAG_LIST, FormatType.class);
cacheTags(BRACKET_TAG_LIST, FormatCloseTag.class);
cacheTags(BRACKET_TAG_LIST, ControlTag.class);
// Interactive tags
cacheTags(INTERACTIVE_TAG_LIST, ClickType.class);
cacheTags(INTERACTIVE_TAG_LIST, HoverType.class);
}
/**
* Puts all constants in the given Tag class into the given lookup table.
*/
private static <T extends Tag> void cacheTags(HashMap<String, Tag> tagList, Class<T> tags) {
for(Tag tag : tags.getEnumConstants()) {
for(String key : tag.getTags()) {
tagList.put(key, tag);
}
}
}
// ------------------------------------------------------------------------------------------
// ------------------------------- Public / Interface -------------------------------
// ------------------------------------------------------------------------------------------
/**
* Parses the given FancyMessageFormat message to a JSON array that can be
* used with the tellraw command and the like.
*/
public static String convertToJSON(final String message) {
return convertToJSON(Collections.singleton(message));
}
/**
* Parses the given FancyMessageFormat message to a JSON array that can be
* used with the tellraw command and the like.
* @param inputLines Input message split at line breaks.
*/
public static String convertToJSON(final Iterable<String> inputLines) {
ArrayList<String> lines = cleanInputString(inputLines);
LinkedList<InteractiveMessagePart> message = parse(lines);
StringBuilder sb = new StringBuilder();
if(message.size() == 1) {
sb.append(message.getFirst().toJSON());
} else if(message.size() > 0) {
sb.append("{text=\"\",extra:[");
for(InteractiveMessagePart messagePart : message) {
sb.append(messagePart.toJSON());
sb.append(',');
}
sb.deleteCharAt(sb.length()-1);
sb.append("]}");
}
return sb.toString();
}
/**
* Parses the given FancyMessageFormat message to a String containing control characters
* for formatting that can be used for console outputs, but also for normal player
* messages.
* <p>
* The returned message will only contain colors, bold, italic, underlining and 'magic'
* characters. Hovers and other advanced tellraw tags will be skipped.
* @param message Input message split at line breaks.
*/
public static String convertToConsole(final String message) {
return convertToConsole(Collections.singleton(message));
}
/**
* Parses the given FancyMessageFormat message to a String containing control characters
* for formatting that can be used for console outputs, but also for normal player
* messages.
* <p>
* The returned message will only contain colors, bold, italic, underlining and 'magic'
* characters. Hovers and other advanced tellraw tags will be skipped.
*/
public static String convertToConsole(final Iterable<String> inputLines) {
if(inputLines == null) {
return null;
}
LinkedList<InteractiveMessagePart> parts = parse(inputLines, false);
StringBuilder result = new StringBuilder();
for(InteractiveMessagePart part : parts) {
result.append(part.toSimpleString());
}
return result.toString();
}
/**
* Insert a message at the specified position
* @param message The current message
* @param insert The message to insert
* @param line The line number to insert at
* @param start The start of the variable to replace
* @param end The end of the variable to replace
*/
public static void insertMessage(List<String> message, List<String> insert, int line, int start, int end) {
String lineContent = message.remove(line);
if(isTaggedInteractive(lineContent)) {
lineContent = lineContent.replace("", "");
message.add(line, lineContent.substring(0, start)+convertToConsole(insert)+lineContent.substring(end));
return;
}
// Find interactive lines meant for this message
List<String> interactives = new ArrayList<>();
int index = line;
while(index < message.size() && isTaggedInteractive(message.get(index))) {
interactives.add(message.get(index));
index++;
}
// Split the line and add the parts
int at = line;
if(start > 0) {
message.add(line, lineContent.substring(0, start));
at++;
message.addAll(at, interactives);
at += interactives.size();
}
message.addAll(at, insert);
at += insert.size();
message.addAll(at, interactives);
at += interactives.size();
if(end < (lineContent.length()-1)) {
message.add(at, lineContent.substring(end));
at++;
message.addAll(at, interactives);
}
}
// ------------------------------------------------------------------------------------------
// ------------------------------- Private functions -------------------------------
// ------------------------------------------------------------------------------------------
/**
* <ul>
* <li>Splits lines at line breaks (creating a new line in the Array).
* <li>Removes empty lines at the beginning.
* <li>Removes lines with properties in front of the first text-line.
* </ul>
*/
private static ArrayList<String> cleanInputString(Iterable<String> inputLines) {
// Split lines at line breaks
// In the end we will have a list with one line per element
ArrayList<String> lines = new ArrayList<>();
for(String line : inputLines) {
lines.addAll(Arrays.asList(line.split("\\r?\\n")));
}
// Remove any special lines at the start (a real text line should be first)
while(!lines.isEmpty() && isTaggedInteractive(lines.get(0))) {
lines.remove(0);
}
return lines;
}
private static LinkedList<InteractiveMessagePart> parse(Iterable<String> inputLines) {
return parse(inputLines, true);
}
private static LinkedList<InteractiveMessagePart> parse(final Iterable<String> inputLines, boolean doInteractives) {
LinkedList<InteractiveMessagePart> message = new LinkedList<>();
Color currentColor = null;
Set<FormatType> currentFormatting = new HashSet<>();
lineLoop:
for(String line : inputLines) {
InteractiveMessagePart messagePart;
TaggedContent interactiveTag = getInteractiveTag(line);
boolean isTextLine = interactiveTag == null;
if(!doInteractives && !isTextLine) {
continue;
}
boolean isHoverLine = false;
if(isTextLine) {
messagePart = new InteractiveMessagePart();
message.add(messagePart);
} else /* if Interactive formatting */ {
messagePart = message.getLast();
Tag tag = interactiveTag.tag;
if(tag instanceof ClickType) {
messagePart.clickType = (ClickType)interactiveTag.tag;
messagePart.clickContent = interactiveTag.subsequentContent;
} else if(tag instanceof HoverType) {
line = interactiveTag.subsequentContent;
isHoverLine = true;
if(messagePart.hoverType != tag) {
// Hover type changed
messagePart.hoverContent = new LinkedList<>();
messagePart.hoverType = (HoverType)tag;
}
// Add hover content below
}
}
if(isTextLine || isHoverLine) {
// Parse inline tags
Color currentLineColor = currentColor;
Set<FormatType> currentLineFormatting = currentFormatting;
LinkedList<TextMessagePart> targetList = messagePart.content;
boolean parseBreak = true;
if(isHoverLine) {
// Reset - use own
currentLineColor = null;
currentLineFormatting = new HashSet<>();
targetList = messagePart.hoverContent;
parseBreak = false;
// Add line break after previous hover line
if(!targetList.isEmpty()) {
targetList.getLast().text += '\n';
}
}
// Split into pieces at places where formatting changes
while(!line.isEmpty()) {
String textToAdd;
TaggedContent nextTag = getNextTag(line, parseBreak);
boolean tagged = nextTag != null;
if(!tagged) {
textToAdd = line;
line = "";
} else {
textToAdd = nextTag.precedingContent;
line = nextTag.subsequentContent;
}
if(!textToAdd.isEmpty()) {
// Add a text part with the correct formatting
TextMessagePart part = new TextMessagePart();
part.text = textToAdd;
part.formatTypes = new HashSet<>(currentLineFormatting);
part.color = currentLineColor;
targetList.add(part);
}
// Handle the change in formatting if a Tag has been detected (this needs to be after creating the InteractiveMessagePart)
if(tagged) {
// Handle the formatting tag
Tag tag = nextTag.tag;
if(tag instanceof Color) {
currentLineColor = (Color)tag;
} else if(tag instanceof FormatType) {
currentLineFormatting.add((FormatType)tag);
} else if(tag instanceof FormatCloseTag) {
currentLineFormatting.remove(((FormatCloseTag)tag).closes);
} else if(tag == ControlTag.BREAK) {
targetList.getLast().text += '\n';
continue lineLoop;
} else if(tag == ControlTag.RESET) {
currentLineFormatting.clear();
currentLineColor = Color.WHITE;
}
}
}
if(!isHoverLine) {
// Adapt global attributes
currentColor = currentLineColor;
if(messagePart.content.size() == 0) { // Prevent interactive parts without content
message.removeLast();
}
}
}
}
return message;
}
/**
* Searches and returns the first continuous tag found in the given String.
* @return The tag (plus its preceding and subsequent content) if found.
* Null if nothing is found.
*/
private static TaggedContent getNextTag(String line, boolean parseBreak) {
Pattern pattern = Pattern.compile("\\[[a-zA-Z1-9]\\]|&[1-9abcdeflonskr]");
Matcher matcher = pattern.matcher(line);
// TODO Fix for escape things, and something with parseBreak?
if(matcher.find()) {
Tag tag = null;
if(matcher.group().startsWith("&")) {
for(Color color : Color.class.getEnumConstants()) {
if(color.getNativeFormattingCode() == matcher.group().charAt(1)) {
tag = color;
}
}
for(FormatType format : FormatType.class.getEnumConstants()) {
if(format.getNativeFormattingCode() == matcher.group().charAt(1)) {
tag = format;
}
}
if(matcher.group().charAt(1) == 'r') {
tag = ControlTag.RESET;
}
} else {
tag = BRACKET_TAG_LIST.get(matcher.group().substring(1, matcher.group().length()-1).toLowerCase());
}
return new TaggedContent(line.substring(0, matcher.start()), tag, line.substring(matcher.end()));
}
return null;
/*
for(int startIndex = 0; startIndex < line.length(); startIndex++) {
int start = line.indexOf(TAG_BEFORE, startIndex);
if(start != -1) {
int end = line.indexOf(TAG_AFTER, start);
if(end != -1) {
String inBetween = line.substring(start+1, end).toLowerCase();
if(BRACKET_TAG_LIST.containsKey(inBetween)) {
Tag tag = BRACKET_TAG_LIST.get(inBetween);
if(tag == ControlTag.ESCAPE) {
// Ignore next char
line = line.substring(0, start)+line.substring(end+1);
startIndex = start;
} else if(!parseBreak && tag == ControlTag.ESCAPE) {
// Ignore break
startIndex = end+1;
} else {
String previousContent = line.substring(0, start);
String subsequentContent = line.substring(end+1);
return new TaggedContent(previousContent, tag, subsequentContent);
}
} else {
startIndex = start;
}
} else {
return null;
}
} else {
return null;
}
}
return null;
*/
}
/**
* If the given line defines an interactive property (e.g. "hover: myText")
* the tag / property will be returned. Otherwise null is returned.
*/
private static TaggedContent getInteractiveTag(String line) {
for(int index = 0; index < line.length(); index++) {
char c = line.charAt(index);
if(c == ' ' || c == '\t') {
// Ignore (Skip spacing)
} else {
int end = line.indexOf(": ", index);
if(end != -1) {
String inBetween = line.substring(index, end).toLowerCase();
if(INTERACTIVE_TAG_LIST.containsKey(inBetween)) {
Tag tag = INTERACTIVE_TAG_LIST.get(inBetween);
String subsequentContent = line.substring(end+2);
return new TaggedContent(null, tag, subsequentContent);
}
}
return null;
}
}
return null;
}
/**
* Check if a line is an advanced declaration like hover or command
* @param line The line to check
* @return true if the line is interactive, false when it is a text line
*/
public static boolean isTaggedInteractive(String line) {
return getInteractiveTag(line) != null;
}
/**
* Produce a string in double quotes with backslash sequences in all the
* right places.
* @param string A String
* @return A String correctly formatted for insertion in a JSON text.
*/
/*
* Copyright (c) 2002 JSON.org
* Licensed under the Apache License, Version 2.0
*/
private static String quoteStringJson(String string) {
if(string == null || string.length() == 0) {
return "\"\"";
}
char c;
int i;
int len = string.length();
StringBuilder sb = new StringBuilder(len+4);
String t;
sb.append('"');
for(i = 0; i < len; i += 1) {
c = string.charAt(i);
switch(c) {
case '\\':
case '"':
sb.append('\\');
sb.append(c);
break;
case '/':
sb.append('\\');
sb.append(c);
break;
case '\b':
sb.append("\\b");
break;
case '\t':
sb.append("\\t");
break;
case '\n':
sb.append("\\n");
break;
case '\f':
sb.append("\\f");
break;
case '\r':
sb.append("\\r");
break;
default:
if(c < ' ') {
t = "000"+Integer.toHexString(c);
sb.append("\\u");
sb.append(t.substring(t.length()-4));
} else {
sb.append(c);
}
}
}
sb.append('"');
return sb.toString();
}
// ------------------------------------------------------------------------------------------
// ------------------------------- Helper classes -------------------------------
// ------------------------------------------------------------------------------------------
private static class TaggedContent {
final String precedingContent;
final Tag tag;
final String subsequentContent;
public TaggedContent(String pre, Tag tag, String sub) {
this.precedingContent = pre;
this.tag = tag;
this.subsequentContent = sub;
}
}
/**
* Holds a string with basic (non-interactive) formatting.
*/
private static class TextMessagePart {
String text = "";
Color color = null;
Set<FormatType> formatTypes = new HashSet<>();
/**
* Get a simple colored/formatted string
* @return String with formatting
*/
String toSimpleString() {
StringBuilder sb = new StringBuilder();
// Color
if(color != null) {
sb.append(SIMPLE_FORMAT_PREFIX_CHAR);
sb.append(color.getNativeFormattingCode());
}
// Formatting
for(FormatType format : formatTypes) {
sb.append(SIMPLE_FORMAT_PREFIX_CHAR);
sb.append(format.getNativeFormattingCode());
}
// Text
sb.append(text);
return sb.toString();
}
/**
* Get a JSON component for this message part
* @return This part formatted in JSON
*/
String toJSON() {
StringBuilder sb = new StringBuilder();
sb.append('{');
sb.append("text:").append(quoteStringJson(text));
if(color != null && color != Color.WHITE) {
sb.append(",color:").append(color.jsonValue);
}
for(FormatType formatting : formatTypes) {
sb.append(',');
sb.append(formatting.jsonKey).append(':');
sb.append("true");
}
sb.append('}');
return sb.toString();
}
boolean hasFormatting() {
return !(color == Color.WHITE && formatTypes.isEmpty());
}
@Override
public String toString() {
return "TextMessagePart(text:"+text+", color:"+color+", formatting:"+formatTypes.toString()+")";
}
}
/**
* Holds a string with interactive formatting.
*/
private static class InteractiveMessagePart {
LinkedList<TextMessagePart> content = new LinkedList<>();
// Click
ClickType clickType = null;
String clickContent = "";
// Hover
HoverType hoverType = null;
LinkedList<TextMessagePart> hoverContent = null;
/**
* Get a simple colored/formatted string
* @return String with formatting
*/
String toSimpleString() {
StringBuilder sb = new StringBuilder();
for(TextMessagePart part : content) {
sb.append(part.toSimpleString());
}
return sb.toString();
}
/**
* Get a JSON component for this message part
* @return This part formatted in JSON
*/
String toJSON() {
StringBuilder sb = new StringBuilder();
if(content.size() == 1) {
// Add attributes to TextMessagePart object
sb.append(content.getFirst().toJSON());
sb.deleteCharAt(sb.length()-1);
} else {
sb.append('{');
sb.append("text=\"\",extra:[");
for(TextMessagePart textPart : content) {
sb.append(textPart.toJSON());
sb.append(',');
}
sb.deleteCharAt(sb.length()-1);
sb.append(']');
}
if(clickType != null) {
sb.append(',');
sb.append("clickEvent:{");
sb.append("action:").append(clickType.getJsonKey()).append(',');
sb.append("value:").append(quoteStringJson(clickContent));
sb.append('}');
}
if(hoverType != null) {
sb.append(',');
sb.append("hoverEvent:{");
sb.append("action:").append(hoverType.getJsonKey()).append(',');
sb.append("value:");
if(hoverContent.size() == 1) {
TextMessagePart hoverPart = hoverContent.getFirst();
if(hoverPart.hasFormatting()) {
sb.append(hoverPart.toJSON());
} else {
sb.append(quoteStringJson(hoverPart.text));
}
} else {
sb.append('[');
for(TextMessagePart hoverPart : hoverContent) {
sb.append(hoverPart.toJSON());
sb.append(',');
}
sb.deleteCharAt(sb.length()-1);
sb.append(']');
}
sb.append('}');
}
sb.append('}');
return sb.toString();
}
@Override
public String toString() {
return "InteractiveMessagePart(textMessageParts:"+content+", clickType:"+clickType+", clickContent:"+clickContent+", hoverType:"+hoverType+", hoverContent:"+hoverContent+")";
}
}
// --------------------------------------- Tags ---------------------------------------
interface Tag {
/**
* Tag text(s) used in the FancyMessageFormat (The text between '[' and ']')
*/
String[] getTags();
}
/**
* Indicates formatting that is applied until explicitly stopped.
* Can also be used in simple Minecraft messages (Non-JSON).
*/
interface ContinuousTag extends Tag {
/**
* The character that defines upcoming formatting in a native (non-JSON) Minecraft message.
*/
char getNativeFormattingCode();
}
/**
* Indicates formatting that allows cursor interaction. Requires the
* Minecraft JSON / tellraw format.
*/
interface InteractiveMessageTag extends Tag {
String getJsonKey();
}
enum Color implements ContinuousTag {
WHITE('f'),
BLACK('0'),
BLUE('9'),
DARK_BLUE('1'),
GREEN('a'),
DARK_GREEN('2'),
AQUA('b'),
DARK_AQUA('3'),
RED('c'),
DARK_RED('4'),
LIGHT_PURPLE('d'),
DARK_PURPLE('5'),
YELLOW('e'),
GOLD('6'),
GRAY('7'),
DARK_GRAY('8');
final char bytecode;
final String jsonValue;
final String[] tags;
Color(char bytecode) {
this.bytecode = bytecode;
this.jsonValue = this.name().toLowerCase();
this.tags = new String[]{this.name().toLowerCase()};
}
@Override
public String[] getTags() {
return tags;
}
@Override
public char getNativeFormattingCode() {
return bytecode;
}
}
enum FormatType implements ContinuousTag {
BOLD('l', "bold", "b", "bold"),
ITALIC('o', "italic", "i", "italic"),
UNDERLINE('n', "underlined", "u", "underline"),
STRIKETHROUGH('s', "strikethrough", "s", "strikethrough"),
OBFUSCATE('k', "obfuscated", "obfuscate");
final char bytecode;
final String jsonKey;
final String[] tags;
FormatType(char bytecode, String jsonKey, String... tags) {
this.bytecode = bytecode;
this.jsonKey = jsonKey;
this.tags = tags;
}
@Override
public String[] getTags() {
return tags;
}
@Override
public char getNativeFormattingCode() {
return bytecode;
}
}
enum FormatCloseTag implements Tag {
BOLD_END(FormatType.BOLD),
ITALIC_END(FormatType.ITALIC),
UNDERLINE_END(FormatType.UNDERLINE),
STRIKETHROUGH_END(FormatType.STRIKETHROUGH),
OBFUSCATE_END(FormatType.OBFUSCATE);
/**
* Formatting that is stopped at this point
*/
final FormatType closes;
private final String[] tags;
FormatCloseTag(FormatType openingTag) {
this.closes = openingTag;
// Auto-generate close tags
tags = new String[closes.tags.length];
for(int i = 0; i < tags.length; i++) {
tags[i] = END_TAG_INDICATOR+closes.tags[i];
}
}
@Override
public String[] getTags() {
return tags;
}
}
enum ControlTag implements Tag {
BREAK("break"),
ESCAPE("esc"),
RESET("reset");
private final String[] tags;
ControlTag(String... tags) {
this.tags = tags;
}
@Override
public String[] getTags() {
return tags;
}
}
/**
* Types of clicking
*/
enum ClickType implements InteractiveMessageTag {
LINK("open_url", "link"),
COMMAND("run_command", "command"),
SUGGEST("suggest_command", "suggest");
private final String jsonKey;
private final String[] tags;
ClickType(String jsonKey, String... tags) {
this.jsonKey = jsonKey;
this.tags = tags;
}
@Override
public String[] getTags() {
return tags;
}
@Override
public String getJsonKey() {
return jsonKey;
}
}
enum HoverType implements InteractiveMessageTag {
HOVER;
@Override
public String[] getTags() {
return new String[]{"hover"};
}
@Override
public String getJsonKey() {
return "show_text";
}
}
}

View File

@ -0,0 +1,91 @@
package nl.evolutioncoding.areashop.messages;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.*;
import java.util.logging.Level;
/**
* Methods written by the Fanciful project (Github: https://github.com/mkremins/fanciful)
*/
public class FancyMessageSender {
private static Constructor<?> nmsPacketPlayOutChatConstructor;
public static boolean sendJSON(Player player, String jsonString) {
try {
Object handle = Reflection.getHandle(player);
Object connection = Reflection.getField(handle.getClass(), "playerConnection").get(handle);
Reflection.getMethod(connection.getClass(), "sendPacket", Reflection.getNMSClass("Packet")).invoke(connection, createChatPacket(jsonString));
return true;
} catch(IllegalArgumentException e) {
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
} catch(IllegalAccessException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
} catch(InstantiationException e) {
Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e);
} catch(InvocationTargetException e) {
Bukkit.getLogger().log(Level.WARNING, "A error has occured durring invoking of method.", e);
} catch(NoSuchMethodException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e);
} catch(ClassNotFoundException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e);
}
return false;
}
// The ChatSerializer's instance of Gson
private static Object nmsChatSerializerGsonInstance;
private static Method fromJsonMethod;
private static Object createChatPacket(String json) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
if(nmsChatSerializerGsonInstance == null) {
// Find the field and its value, completely bypassing obfuscation
Class<?> chatSerializerClazz;
String version = Reflection.getVersion();
double majorVersion = Double.parseDouble(version.replace('_', '.').substring(1, 4));
int lesserVersion = Integer.parseInt(version.substring(6, 7));
if(majorVersion < 1.8 || (majorVersion == 1.8 && lesserVersion == 1)) {
chatSerializerClazz = Reflection.getNMSClass("ChatSerializer");
} else {
chatSerializerClazz = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer");
}
if(chatSerializerClazz == null) {
throw new ClassNotFoundException("Can't find the ChatSerializer class");
}
for(Field declaredField : chatSerializerClazz.getDeclaredFields()) {
if(Modifier.isFinal(declaredField.getModifiers()) && Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType().getName().endsWith("Gson")) {
// We've found our field
declaredField.setAccessible(true);
nmsChatSerializerGsonInstance = declaredField.get(null);
fromJsonMethod = nmsChatSerializerGsonInstance.getClass().getMethod("fromJson", String.class, Class.class);
break;
}
}
}
// Since the method is so simple, and all the obfuscated methods have the same name, it's easier to reimplement 'IChatBaseComponent a(String)' than to reflectively call it
// Of course, the implementation may change, but fuzzy matches might break with signature changes
Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, Reflection.getNMSClass("IChatBaseComponent"));
if(nmsPacketPlayOutChatConstructor == null) {
try {
nmsPacketPlayOutChatConstructor = Reflection.getNMSClass("PacketPlayOutChat").getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent"));
nmsPacketPlayOutChatConstructor.setAccessible(true);
} catch(NoSuchMethodException e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not find Minecraft method or constructor.", e);
} catch(SecurityException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access constructor.", e);
}
}
return nmsPacketPlayOutChatConstructor.newInstance(serializedChatComponent);
}
}

View File

@ -0,0 +1,164 @@
package nl.evolutioncoding.areashop.messages;
import com.google.common.base.Charsets;
import nl.evolutioncoding.areashop.AreaShop;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.*;
import java.util.*;
import static nl.evolutioncoding.areashop.messages.Message.CHATLANGUAGEVARIABLE;
public class LanguageManager {
private AreaShop plugin = null;
private String languages[] = {"EN", "NL", "DE", "CS", "FR", "FI", "PL"};
private Map<String, List<String>> currentLanguage, defaultLanguage;
/**
* Constructor
* @param plugin The AreaShop plugin
*/
public LanguageManager(AreaShop plugin) {
this.plugin = plugin;
startup();
}
/**
* Save the default language files and open the current and backup language file
*/
public void startup() {
this.saveDefaults();
currentLanguage = loadLanguage(plugin.getConfig().getString("language"));
defaultLanguage = loadLanguage(languages[0]);
}
/**
* Saves the default language files if not already present
*/
public void saveDefaults() {
// Create the language folder if it not exists
File langFolder;
langFolder = new File(plugin.getDataFolder()+File.separator+AreaShop.languageFolder);
if(!langFolder.exists()) {
if(!langFolder.mkdirs()) {
plugin.getLogger().warning("Could not create language directory: "+langFolder.getAbsolutePath());
return;
}
}
// Create the language files, overwrites if a file already exists
// Overriding is necessary because otherwise with an update the new lang
// files would not be used, when translating your own use another
// file name as the default
File langFile;
for(String language : languages) {
langFile = new File(plugin.getDataFolder()+File.separator+AreaShop.languageFolder+File.separator+language+".yml");
try(
InputStream input = plugin.getResource(AreaShop.languageFolder+"/"+language+".yml");
OutputStream output = new FileOutputStream(langFile)
) {
if(input == null) {
plugin.getLogger().warning("Could not save default language to the '"+AreaShop.languageFolder+"' folder: "+language+".yml");
continue;
}
int read;
byte[] bytes = new byte[1024];
while((read = input.read(bytes)) != -1) {
output.write(bytes, 0, read);
}
input.close();
output.close();
} catch(IOException e) {
plugin.getLogger().warning("Something went wrong saving a default language file: "+langFile.getPath());
}
}
}
/**
* Loads the specified language
* @param key The language to load
*/
public Map<String, List<String>> loadLanguage(String key) {
Map<String, List<String>> result = new HashMap<>();
// Load the strings
File file = new File(plugin.getDataFolder()+File.separator+AreaShop.languageFolder+File.separator+key+".yml");
try(
InputStreamReader reader = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)
) {
YamlConfiguration ymlFile = YamlConfiguration.loadConfiguration(reader);
if(ymlFile.getKeys(false).isEmpty()) {
plugin.getLogger().warning("Language file "+key+".yml has zero messages.");
return result;
}
for(String messageKey : ymlFile.getKeys(false)) {
if(ymlFile.isList(messageKey)) {
result.put(messageKey, new ArrayList<>(ymlFile.getStringList(messageKey)));
} else {
result.put(messageKey, new ArrayList<>(Collections.singletonList(ymlFile.getString(messageKey))));
}
}
} catch(IOException e) {
plugin.getLogger().warning("Could not load set language file: "+file.getAbsolutePath());
}
return result;
}
/**
* Get the message for a certain key, without doing any processing
* @param key The key of the message to get
* @return The message as a list of strings
*/
public List<String> getRawMessage(String key) {
List<String> message;
if(key.equalsIgnoreCase(CHATLANGUAGEVARIABLE)) {
message = plugin.getChatPrefix();
} else if(currentLanguage.containsKey(key)) {
message = currentLanguage.get(key);
} else {
message = defaultLanguage.get(key);
}
if(message == null) {
return new ArrayList<>();
}
return new ArrayList<>(message);
}
}

View File

@ -0,0 +1,282 @@
package nl.evolutioncoding.areashop.messages;
import nl.evolutioncoding.areashop.AreaShop;
import nl.evolutioncoding.areashop.regions.GeneralRegion;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Message {
public static final String VARIABLESTART = "%";
public static final String VARIABLEEND = "%";
public static final String LANGUAGEVARIABLE = "lang:";
public static final String CHATLANGUAGEVARIABLE = "prefix";
public static final int REPLACEMENTLIMIT = 50;
private List<String> message;
private Object[] replacements;
private String key = null;
/**
* Internal use only
*/
private Message() {
message = new ArrayList<>();
}
/**
* Empty message object
* @return this
*/
public static Message none() {
return new Message();
}
/**
* Construct a message from a language key
* @param key The key of the message to use
* @return this
*/
public static Message fromKey(String key) {
return new Message().setMessageFromKey(key);
}
/**
* Construct a message from a string
* @param message The message to use
* @return this
*/
public static Message fromString(String message) {
AreaShop.debug("fromString: "+message);
return new Message().setMessage(message);
}
/**
* Construct a message from a string list
* @param message The message to use
* @return this
*/
public static Message fromString(List<String> message) {
return new Message().setMessage(message);
}
/**
* Get the message with all replacements done
* @return Message as a list
*/
public List<String> get() {
executeReplacements();
return message;
}
/**
* Get a plain string for the message (for example for using in the console)
* @return The message as simple string
*/
public String getPlain() {
executeReplacements();
return FancyMessageFormat.convertToConsole(message);
}
/**
* Add the default prefix to the message
* @param doIt true if the prefix should be added, otherwise false
* @return this
*/
public Message prefix(boolean doIt) {
if(doIt) {
message.add(0, VARIABLESTART+LANGUAGEVARIABLE+CHATLANGUAGEVARIABLE+VARIABLEEND);
}
return this;
}
public Message prefix() {
return prefix(true);
}
/**
* Set the replacements to apply to the message
* @param replacements The replacements to apply
* - GeneralRegion: All region replacements are applied
* - Message: Message is inserted
* - other: index tag is replaced, like %0%
* @return this
*/
public Message replacements(Object... replacements) {
this.replacements = replacements;
return this;
}
/**
* Send the message to a target
* @param target The target to send the message to (Player, CommandSender, Logger)
* @return this
*/
public Message send(Object target) {
if(message == null || message.size() == 0 || (message.size() == 1 && message.get(0).length() == 0)) {
return this;
}
executeReplacements();
if(target instanceof Player) {
if(AreaShop.getInstance().getConfig().getBoolean("useFancyMessages")) {
FancyMessageSender.sendJSON((Player)target, FancyMessageFormat.convertToJSON(message));
} else {
((Player)target).sendMessage(FancyMessageFormat.convertToConsole(message));
}
} else {
String plainMessage = FancyMessageFormat.convertToConsole(message);
if(!AreaShop.getInstance().getConfig().getBoolean("useColorsInConsole")) {
plainMessage = ChatColor.stripColor(plainMessage);
}
if(target instanceof CommandSender) {
((CommandSender)target).sendMessage(plainMessage);
} else if(target instanceof Logger) {
((Logger)target).info(plainMessage);
} else {
AreaShop.getInstance().getLogger().warning("Could not send message, target is wrong: "+plainMessage);
}
}
return this;
}
// INTERNAL METHODS
/**
* Set the internal message
* @param message The message to set
* @return this
*/
private Message setMessage(List<String> message) {
this.message = message;
if(this.message == null) {
this.message = new ArrayList<>();
}
return this;
}
/**
* Set the internal message with a key
* @param key The message key to get the message for
* @return this
*/
private Message setMessageFromKey(String key) {
this.key = key;
return this.setMessage(AreaShop.getInstance().getLanguageManager().getRawMessage(key));
}
/**
* Set the internal message with a string
* @param message The message to set
* @return this
*/
private Message setMessage(String message) {
List<String> list = new ArrayList<>();
list.add(message);
return this.setMessage(list);
}
/**
* Apply all replacements to the message
*/
private void executeReplacements() {
// Replace variables until they are all gone, or when the limit is reached
Pattern variable = Pattern.compile(Pattern.quote(VARIABLESTART)+"[^%\\s]+"+Pattern.quote(VARIABLEEND));
int round = 0;
boolean shouldReplace = true;
while(shouldReplace) {
List<String> original = new ArrayList<>(message);
replaceLanguageVariables();
replaceArgumentVariables();
shouldReplace = !message.equals(original);
round++;
if(round > REPLACEMENTLIMIT) {
AreaShop.getInstance().getLogger().warning("Reached replacement limit for message "+key+", probably has replacements loops, resulting message: "+message.toString());
break;
}
}
}
/**
* Replace argument variables in a message
* The arguments to apply as replacements:
* - If it is a GeneralRegion the replacements of the region will be applied
* - Else the parameter will replace its number surrounded with VARIABLESTART and VARIABLEEND
*/
private void replaceArgumentVariables() {
if(message == null || message.size() == 0 || replacements == null) {
return;
}
boolean result = false;
for(int i = 0; i < message.size(); i++) {
int number = 0;
for(Object param : replacements) {
if(param != null) {
if(param instanceof GeneralRegion) {
message.set(i, ((GeneralRegion)param).applyAllReplacements(message.get(i)));
} else if(param instanceof Message) {
Pattern variables = Pattern.compile(Pattern.quote(VARIABLESTART)+i+Pattern.quote(VARIABLEEND));
Matcher matches = variables.matcher(message.get(i));
if(matches.find()) {
String variable = matches.group();
// insert message
FancyMessageFormat.insertMessage(message, ((Message)param).get(), i, matches.start(), matches.end());
// Reset to start of the line, redo matching because the line changed and the inserted part might contain variables again
i--;
}
number++;
} else {
message.set(i, message.get(i).replace(VARIABLESTART+number+VARIABLEEND, param.toString()));
number++;
}
}
}
}
}
/**
* Replace all language variables in a message
*/
private void replaceLanguageVariables() {
if(message == null || message.size() == 0) {
return;
}
Pattern variables = Pattern.compile(Pattern.quote(VARIABLESTART)+"lang:[^%\\s]+(\\|[^"+Pattern.quote(VARIABLEEND)+"]*)*"+Pattern.quote(VARIABLEEND)); // Variables cannot contain spaces and percent characters, and area enclosed by percent characters
for(int i = 0; i < message.size(); i++) {
Matcher matches = variables.matcher(message.get(i));
if(matches.find()) {
String variable = matches.group();
String key;
Object[] arguments = null;
if(variable.contains("|")) {
key = variable.substring(6, variable.indexOf("|"));
arguments = variable.substring(variable.indexOf("|")+1, variable.length()-1).split("\\|");
} else {
key = variable.substring(6, variable.length()-1);
}
Message insert = Message.fromKey(key);
if(arguments != null) {
insert.replacements(arguments);
}
// insert message
//List<String> insert = AreaShop.getInstance().getLanguageManager().getRawMessage(variable.substring(6, variable.length()-1));
FancyMessageFormat.insertMessage(message, insert.get(), i, matches.start(), matches.end());
// Reset to start of the line, redo matching because the line changed and the inserted part might contain language tags again
i--;
}
}
}
}

View File

@ -0,0 +1,219 @@
package nl.evolutioncoding.areashop.messages;
import org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Class made by glan3b (Github: https://github.com/glen3b) for the Fanciful project (Github: https://github.com/mkremins/fanciful)
*/
/**
* A class containing static utility methods and caches which are intended as reflective conveniences.
* Unless otherwise noted, upon failure methods will return {@code null}.
*/
public final class Reflection {
private static String _versionString;
private Reflection() {
}
/**
* Gets the version string from the package name of the CraftBukkit server implementation.
* This is needed to bypass the JAR package name changing on each update.
* @return The version string of the OBC and NMS packages, <em>including the trailing dot</em>.
*/
public synchronized static String getVersion() {
if(_versionString == null) {
if(Bukkit.getServer() == null) {
// The server hasn't started, static initializer call?
return null;
}
String name = Bukkit.getServer().getClass().getPackage().getName();
_versionString = name.substring(name.lastIndexOf('.')+1)+".";
}
return _versionString;
}
/**
* Stores loaded classes from the {@code net.minecraft.server} package.
*/
private static final Map<String, Class<?>> _loadedNMSClasses = new HashMap<String, Class<?>>();
/**
* Stores loaded classes from the {@code org.bukkit.craftbukkit} package (and subpackages).
*/
private static final Map<String, Class<?>> _loadedOBCClasses = new HashMap<String, Class<?>>();
/**
* Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
* @param className The name of the class, excluding the package, within NMS.
* @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded.
*/
public synchronized static Class<?> getNMSClass(String className) {
if(_loadedNMSClasses.containsKey(className)) {
return _loadedNMSClasses.get(className);
}
String fullName = "net.minecraft.server."+getVersion()+className;
Class<?> clazz = null;
try {
clazz = Class.forName(fullName);
} catch(Exception e) {
e.printStackTrace();
_loadedNMSClasses.put(className, null);
return null;
}
_loadedNMSClasses.put(className, clazz);
return clazz;
}
/**
* Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
* @param className The name of the class, excluding the package, within OBC. This name may contain a subpackage name, such as {@code inventory.CraftItemStack}.
* @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded.
*/
public synchronized static Class<?> getOBCClass(String className) {
if(_loadedOBCClasses.containsKey(className)) {
return _loadedOBCClasses.get(className);
}
String fullName = "org.bukkit.craftbukkit."+getVersion()+className;
Class<?> clazz = null;
try {
clazz = Class.forName(fullName);
} catch(Exception e) {
e.printStackTrace();
_loadedOBCClasses.put(className, null);
return null;
}
_loadedOBCClasses.put(className, clazz);
return clazz;
}
/**
* Attempts to get the NMS handle of a CraftBukkit object.
* <p>
* The only match currently attempted by this method is a retrieval by using a parameterless {@code getHandle()} method implemented by the runtime type of the specified object.
* </p>
* @param obj The object for which to retrieve an NMS handle.
* @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}.
*/
public synchronized static Object getHandle(Object obj) {
try {
return getMethod(obj.getClass(), "getHandle").invoke(obj);
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
private static final Map<Class<?>, Map<String, Field>> _loadedFields = new HashMap<Class<?>, Map<String, Field>>();
/**
* Retrieves a {@link Field} instance declared by the specified class with the specified name.
* Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
* returned will be an instance or static field.
* <p>
* A global caching mechanism within this class is used to store fields. Combined with synchronization, this guarantees that
* no field will be reflectively looked up twice.
* </p>
* <p>
* If a field is deemed suitable for return, {@link Field#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
* This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
* </p>
* @param clazz The class which contains the field to retrieve.
* @param name The declared name of the field in the class.
* @return A field object with the specified name declared by the specified class.
* @see Class#getDeclaredField(String)
*/
public synchronized static Field getField(Class<?> clazz, String name) {
Map<String, Field> loaded;
if(!_loadedFields.containsKey(clazz)) {
loaded = new HashMap<String, Field>();
_loadedFields.put(clazz, loaded);
} else {
loaded = _loadedFields.get(clazz);
}
if(loaded.containsKey(name)) {
// If the field is loaded (or cached as not existing), return the relevant value, which might be null
return loaded.get(name);
}
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
loaded.put(name, field);
return field;
} catch(Exception e) {
// Error loading
e.printStackTrace();
// Cache field as not existing
loaded.put(name, null);
return null;
}
}
/**
* Contains loaded methods in a cache.
* The map maps [types to maps of [method names to maps of [parameter types to method instances]]].
*/
private static final Map<Class<?>, Map<String, Map<ArrayWrapper<Class<?>>, Method>>> _loadedMethods = new HashMap<Class<?>, Map<String, Map<ArrayWrapper<Class<?>>, Method>>>();
/**
* Retrieves a {@link Method} instance declared by the specified class with the specified name and argument types.
* Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
* returned will be an instance or static field.
* <p>
* A global caching mechanism within this class is used to store method. Combined with synchronization, this guarantees that
* no method will be reflectively looked up twice.
* </p>
* <p>
* If a method is deemed suitable for return, {@link Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
* This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
* </p>
* <p>
* This method does <em>not</em> search superclasses of the specified type for methods with the specified signature.
* Callers wishing this behavior should use {@link Class#getDeclaredMethod(String, Class...)}.
* @param clazz The class which contains the method to retrieve.
* @param name The declared name of the method in the class.
* @param args The formal argument types of the method.
* @return A method object with the specified name declared by the specified class.
*/
public synchronized static Method getMethod(Class<?> clazz, String name,
Class<?>... args) {
if(!_loadedMethods.containsKey(clazz)) {
_loadedMethods.put(clazz, new HashMap<String, Map<ArrayWrapper<Class<?>>, Method>>());
}
Map<String, Map<ArrayWrapper<Class<?>>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
if(!loadedMethodNames.containsKey(name)) {
loadedMethodNames.put(name, new HashMap<ArrayWrapper<Class<?>>, Method>());
}
Map<ArrayWrapper<Class<?>>, Method> loadedSignatures = loadedMethodNames.get(name);
ArrayWrapper<Class<?>> wrappedArg = new ArrayWrapper<Class<?>>(args);
if(loadedSignatures.containsKey(wrappedArg)) {
return loadedSignatures.get(wrappedArg);
}
for(Method m : clazz.getMethods()) {
if(m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) {
m.setAccessible(true);
loadedSignatures.put(wrappedArg, m);
return m;
}
}
loadedSignatures.put(wrappedArg, null);
return null;
}
}

View File

@ -243,7 +243,7 @@ public class BuyRegion extends GeneralRegion {
* @return String indicating the inactive time until unrent
*/
public String getFormattedInactiveTimeUntilSell() {
return this.millisToHumanFormat(getInactiveTimeUntilSell());
return Utils.millisToHumanFormat(getInactiveTimeUntilSell());
}
/**

View File

@ -9,6 +9,7 @@ import nl.evolutioncoding.areashop.events.NotifyAreaShopEvent;
import nl.evolutioncoding.areashop.events.notify.RegionUpdateEvent;
import nl.evolutioncoding.areashop.interfaces.GeneralRegionInterface;
import nl.evolutioncoding.areashop.managers.FileManager;
import nl.evolutioncoding.areashop.messages.Message;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.*;
import org.bukkit.block.Block;
@ -23,8 +24,6 @@ import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class GeneralRegion implements GeneralRegionInterface, Comparable<GeneralRegion> {
YamlConfiguration config;
@ -549,23 +548,6 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
if(source == null || source.length() == 0) {
return "";
}
// Apply language replacements
Pattern regex = Pattern.compile("%lang:[^% [-]]+%");
Matcher matcher = regex.matcher(source);
while(matcher.find()) {
String match = matcher.group();
String key = match.substring(6, match.length()-1);
String languageString;
if(key.equalsIgnoreCase("prefix")) {
languageString = plugin.getChatPrefix();
} else {
languageString = plugin.getLanguageManager().getLang(key);
}
if(languageString != null) {
source = source.replace(match, languageString);
}
//AreaShop.debug("match=" + match + ", key=" + key + ", lanString=" + languageString + ", replaced=" + source);
}
// Apply static replacements
HashMap<String, Object> replacements = getAllReplacements();
for(String tag : replacements.keySet()) {
@ -781,7 +763,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
Object[] newParams = new Object[params.length + 1];
newParams[0] = this;
System.arraycopy(params, 0, newParams, 1, params.length);
plugin.configurableMessage(target, key, prefix, newParams);
Message.fromKey(key).prefix(prefix).replacements(newParams).send(target);
}
public void messageNoPrefix(Object target, String key, Object... params) {
@ -892,47 +874,6 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
return result;
}
/**
* Convert milliseconds to a human readable format
* @param milliseconds The amount of milliseconds to convert
* @return A formatted string based on the language file
*/
public String millisToHumanFormat(long milliseconds) {
long timeLeft = milliseconds + 500;
// To seconds
timeLeft = timeLeft/1000;
if(timeLeft <= 0) {
return plugin.getLanguageManager().getLang("timeleft-ended");
} else if(timeLeft == 1) {
return plugin.getLanguageManager().getLang("timeleft-second", timeLeft);
} else if(timeLeft <= 120) {
return plugin.getLanguageManager().getLang("timeleft-seconds", timeLeft);
}
// To minutes
timeLeft = timeLeft/60;
if(timeLeft <= 120) {
return plugin.getLanguageManager().getLang("timeleft-minutes", timeLeft);
}
// To hours
timeLeft = timeLeft/60;
if(timeLeft <= 48) {
return plugin.getLanguageManager().getLang("timeleft-hours", timeLeft);
}
// To days
timeLeft = timeLeft/24;
if(timeLeft <= 60) {
return plugin.getLanguageManager().getLang("timeleft-days", timeLeft);
}
// To months
timeLeft = timeLeft/30;
if(timeLeft <= 24) {
return plugin.getLanguageManager().getLang("timeleft-months", timeLeft);
}
// To years
timeLeft = timeLeft/12;
return plugin.getLanguageManager().getLang("timeleft-years", timeLeft);
}
/**
* Reset all flags of the region
*/
@ -1123,7 +1064,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
if(safeLocation.getBlockY()>256 || safeLocation.getBlockY()<0) {
continue;
}
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1141,7 +1082,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
if(safeLocation.getBlockY()>256 || safeLocation.getBlockY()<0) {
continue;
}
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1159,7 +1100,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
if(safeLocation.getBlockY()>256 || safeLocation.getBlockY()<0) {
continue;
}
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1177,7 +1118,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
if(safeLocation.getBlockY()>256 || safeLocation.getBlockY()<0) {
continue;
}
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1195,7 +1136,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
}
if(!done && !top) {
safeLocation = startLocation.clone().add(0, radius, 0);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1206,7 +1147,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// North
for(int x=-r+1; x<=r && !done; x++) {
safeLocation = startLocation.clone().add(x, radius, -r);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1216,7 +1157,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// East
for(int z=-r+1; z<=r && !done; z++) {
safeLocation = startLocation.clone().add(r, radius, z);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1226,7 +1167,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// South side
for(int x=r-1; x>=-r && !done; x--) {
safeLocation = startLocation.clone().add(x, radius, r);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1236,7 +1177,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// West side
for(int z=r-1; z>=-r && !done; z--) {
safeLocation = startLocation.clone().add(-r, radius, z);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1254,7 +1195,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
}
if(!done && !bottom) {
safeLocation = startLocation.clone().add(0, -radius, 0);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1265,7 +1206,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// North
for(int x=-r+1; x<=r && !done; x++) {
safeLocation = startLocation.clone().add(x, -radius, -r);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1275,7 +1216,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// East
for(int z=-r+1; z<=r && !done; z++) {
safeLocation = startLocation.clone().add(r, -radius, z);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1285,7 +1226,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// South side
for(int x=r-1; x>=-r && !done; x--) {
safeLocation = startLocation.clone().add(x, -radius, r);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;
@ -1295,7 +1236,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// West side
for(int z=r-1; z>=-r && !done; z--) {
safeLocation = startLocation.clone().add(-r, -radius, z);
if((insideRegion && region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ())) || !insideRegion) {
if(region.contains(safeLocation.getBlockX(), safeLocation.getBlockY(), safeLocation.getBlockZ()) || !insideRegion) {
checked++;
done = isSafe(safeLocation) || checked > maxTries;
blocksInRegion = true;

View File

@ -21,6 +21,8 @@ import java.util.Date;
import java.util.HashMap;
import java.util.UUID;
import static nl.evolutioncoding.areashop.Utils.millisToHumanFormat;
public class RentRegion extends GeneralRegion {
private long warningsDoneUntil = Calendar.getInstance().getTimeInMillis();
@ -154,7 +156,7 @@ public class RentRegion extends GeneralRegion {
}
result.put(AreaShop.tagMaxExtends, this.getMaxExtends());
result.put(AreaShop.tagExtendsLeft, getMaxExtends() - getTimesExtended());
result.put(AreaShop.tagMaxRentTime, this.millisToHumanFormat(getMaxRentTime()));
result.put(AreaShop.tagMaxRentTime, millisToHumanFormat(getMaxRentTime()));
result.put(AreaShop.tagMaxInactiveTime, this.getFormattedInactiveTimeUntilUnrent());
return result;
}
@ -251,7 +253,7 @@ public class RentRegion extends GeneralRegion {
* @return Time left on the rent, for example '29 days', '3 months', '1 second'
*/
public String getTimeLeftString() {
return millisToHumanFormat(getTimeLeft());
return Utils.millisToHumanFormat(getTimeLeft());
}
/**
@ -267,7 +269,7 @@ public class RentRegion extends GeneralRegion {
* @return String indicating the inactive time until unrent
*/
public String getFormattedInactiveTimeUntilUnrent() {
return this.millisToHumanFormat(getInactiveTimeUntilUnrent());
return Utils.millisToHumanFormat(getInactiveTimeUntilUnrent());
}
/**

View File

@ -7,7 +7,12 @@
# │ GENERAL: Options that influence the global state of the plugin │
# └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
## Chatprefix used for all messages in the chat, also changes the greeting messages.
chatPrefix: '&2[AreaShop]&r '
chatPrefix:
- '&2[AreaShop]&r'
- ' hover: &fAreaShop region management plugin'
- ' hover: &fClick to check the available commands'
- ' command: /areashop help'
- ' '
## The language file that should be used, check the 'lang' folder for build-in languages (use the filename without .yml here).
## More information can be found here: https://github.com/NLthijs48/AreaShop/wiki/Language-support.
language: EN
@ -319,6 +324,8 @@ sendStats: true
checkForUpdates: true
## Use colors when sending messages to console and log files.
useColorsInConsole: false
## Use tellraw style messages
useFancyMessages: true
## Post error messages in the console when a command run from the config fails (from the 'runCommands' section for example).
postCommandErrors: true
## Update all region flags and signs after starting the plugin (uses the 'regionsPerTick' setting from the 'update' section).

View File

@ -1,4 +1,4 @@
# ╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
# ╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
# ║ Language file of the AreaShop plugin created by NLThijs48, Github can be found at https://github.com/NLthijs48/AreaShop ║
# ║ Language: English, Version: V2.2.2, Percentage translated: 100% (source), author: NLThijs48 ║
# ║ This file will be overwritten at each startup/reload of the plugin, if you want to change anything then do the following: ║
@ -8,6 +8,11 @@
# ║ 4: Change the strings in the new file to your liking and save the file ║
# ║ 5: Use '/as reload' or reload/restart your server to see the changes ║
# ╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
command:
- "&6%0% &7-&r"
- " hover: &9&l<use %0%>"
- " command: %0%"
total-maximum: "You can't rent and buy more than %0% region(s) in total (you already have %1% in group '%2%')."
general-notReady: "AreaShop has not fully loaded yet, please wait."
@ -29,45 +34,45 @@ cmd-automaticRegionOnlyByPlayer: "Automatically determining the region is only p
help-header: "Help page, commands that you can execute."
help-alias: "Command aliases: /areashop, /as."
help-help: "&6/as help &7-&r Shows this help page."
help-info: "&6/as info &7-&r Get info about current regions."
help-rent: "&6/as rent &7-&r Rent a region or extend your current rent."
help-buy: "&6/as buy &7-&r Buy a region."
help-unrent: "&6/as unrent &7-&r Unrent a region."
help-unrentOwn: "&6/as unrent &7-&r Unrent your own region."
help-sell: "&6/as sell &7-&r Sell a region."
help-sellOwn: "&6/as sell &7-&r Sell your own region."
help-reload: "&6/as reload &7-&r Reload all files and update the regions."
help-setrestore: "&6/as setrestore &7-&r Set restoring on/off and choose profile."
help-setprice: "&6/as setprice &7-&r Change the price of a region."
help-setduration: "&6/as setduration &7-&r Change the duration of a rent region."
help-teleport: "&6/as tp &7-&r Teleport to your bought/rented regions."
help-teleportAll: "&6/as tp &7-&r Teleport to a rent/buy region."
help-setteleport: "&6/as settp &7-&r Set teleport position for bought/rented regions."
help-setteleportAll: "&6/as settp &7-&r Set teleport position for a region."
help-find: "&6/as find &7-&r Find an empty buy or rent."
help-groupadd: "&6/as groupadd &7-&r Add a region to a group."
help-groupdel: "&6/as groupdel &7-&r Delete a region from a group."
help-grouplist: "&6/as grouplist &7-&r Display all groups currently registered."
help-groupinfo: "&6/as groupinfo &7-&r Display information about a group."
help-schemevent: "&6/as schemevent &7-&r Trigger a schematic event for a region."
help-add: "&6/as add &7-&r Register a region as rent or buy."
help-del: "&6/as del &7-&r Delete a registered region from AreaShop."
help-addsign: "&6/as addsign &7-&r Add a sign to an existing region."
help-delsign: "&6/as delsign &7-&r Delete the sign you are looking at."
help-me: "&6/as me &7-&r Check which regions you have (+expiration)."
help-setowner: "&6/as setowner &7-&r Set region owner or extend the rent."
help-resell: "&6/as resell &7-&r Put one of your regions into resell mode."
help-resellAll: "&6/as resell &7-&r Put a region into resell mode."
help-stopResell: "&6/as stopresell &7-&r Put your region back into sold mode."
help-stopResellAll: "&6/as stopresell &7-&r Put a region back into sold mode."
help-addFriend: "&6/as addfriend &7-&r Add a friend to your region."
help-addFriendAll: "&6/as addfriend &7-&r Add a friend to a region."
help-delFriend: "&6/as delfriend &7-&r Delete a friend from your region."
help-delFriendAll: "&6/as delfriend &7-&r Delete a friend from a region."
help-linksigns: "&6/as linksigns &7-&r Use bulk sign linking mode."
help-stack: "&6/as stack &7-&r Create multiple regions and add them."
help-setlandlord: "&6/as setlandlord &7-&r Set the landlord of a region."
help-help: "%lang:command|/as help% Shows this help page."
help-info: "%lang:command|/as info% Get info about current regions."
help-rent: "%lang:command|/as rent% Rent a region or extend your current rent."
help-buy: "%lang:command|/as buy% Buy a region."
help-unrent: "%lang:command|/as unrent% Unrent a region."
help-unrentOwn: "%lang:command|/as unrent% Unrent your own region."
help-sell: "%lang:command|/as sell% Sell a region."
help-sellOwn: "%lang:command|/as sell% Sell your own region."
help-reload: "%lang:command|/as reload% Reload all files and update the regions."
help-setrestore: "%lang:command|/as setrestore% Set restoring on/off and choose profile."
help-setprice: "%lang:command|/as setprice% Change the price of a region."
help-setduration: "%lang:command|/as setduration% Change the duration of a rent region."
help-teleport: "%lang:command|/as tp% Teleport to your bought/rented regions."
help-teleportAll: "%lang:command|/as tp% Teleport to a rent/buy region."
help-setteleport: "%lang:command|/as settp% Set teleport position for bought/rented regions."
help-setteleportAll: "%lang:command|/as settp% Set teleport position for a region."
help-find: "%lang:command|/as find% Find an empty buy or rent."
help-groupadd: "%lang:command|/as groupadd% Add a region to a group."
help-groupdel: "%lang:command|/as groupdel% Delete a region from a group."
help-grouplist: "%lang:command|/as grouplist% Display all groups currently registered."
help-groupinfo: "%lang:command|/as groupinfo% Display information about a group."
help-schemevent: "%lang:command|/as schemevent% Trigger a schematic event for a region."
help-add: "%lang:command|/as add% Register a region as rent or buy."
help-del: "%lang:command|/as del% Delete a registered region from AreaShop."
help-addsign: "%lang:command|/as addsign% Add a sign to an existing region."
help-delsign: "%lang:command|/as delsign% Delete the sign you are looking at."
help-me: "%lang:command|/as me% Check which regions you have (+expiration)."
help-setowner: "%lang:command|/as setowner% Set region owner or extend the rent."
help-resell: "%lang:command|/as resell% Put one of your regions into resell mode."
help-resellAll: "%lang:command|/as resell% Put a region into resell mode."
help-stopResell: "%lang:command|/as stopresell% Put your region back into sold mode."
help-stopResellAll: "%lang:command|/as stopresell% Put a region back into sold mode."
help-addFriend: "%lang:command|/as addfriend% Add a friend to your region."
help-addFriendAll: "%lang:command|/as addfriend% Add a friend to a region."
help-delFriend: "%lang:command|/as delfriend% Delete a friend from your region."
help-delFriendAll: "%lang:command|/as delfriend% Delete a friend from a region."
help-linksigns: "%lang:command|/as linksigns% Use bulk sign linking mode."
help-stack: "%lang:command|/as stack% Create multiple regions and add them."
help-setlandlord: "%lang:command|/as setlandlord% Set the landlord of a region."
rent-help: "/as rent [regionname], the region you stand in will be used if not specified."
rent-noPermission: "You don't have permission to rent a region."
@ -100,7 +105,7 @@ buy-succes: "You successfully bought %region%."
buy-successResale: "You successfully bought %region% from %0%."
buy-successSeller: "Your region %region% has been sold to %player% for %0%."
buy-lowMoney: "You don't have enough money to buy this region (you have %0% and you need %price%)."
buy-lowMoneyResell: "You don't have enugh money to buy this region (you have %0% and you need %resellprice%)."
buy-lowMoneyResell: "You don't have enough money to buy this region (you have %0% and you need %resellprice%)."
buy-yours: "You already own this region."
buy-someoneElse: "Someone else already bought this region."
buy-restrictedToWorld: "You need to be in the '%world%' world to buy this region (you are in '%0%')."