diff --git a/Towny/pom.xml b/Towny/pom.xml
index 04c0f5a7d7..4e18ee06f3 100644
--- a/Towny/pom.xml
+++ b/Towny/pom.xml
@@ -13,7 +13,7 @@
towny
jar
- 0.100.1.4
+ 0.100.1.5
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownySettings.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownySettings.java
index a57e00ddbc..52cadc5e45 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownySettings.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownySettings.java
@@ -2514,8 +2514,8 @@ public static int getMaxResidentsForTown(Town town) {
if (town.isCapital())
return getMaxResidentsPerTownCapitalOverride();
else
- return !town.hasNation() && getMaxNumResidentsWithoutNation() > 0
- ? getMaxNumResidentsWithoutNation()
+ return !town.hasNation()
+ ? town.getMaxAllowedNumberOfResidentsWithoutNation()
: getMaxResidentsPerTown();
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/NationCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/NationCommand.java
index 7c66c69e4c..567d121794 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/NationCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/NationCommand.java
@@ -493,8 +493,7 @@ public void parseNationCommand(final Player player, String[] split) throws Towny
break;
case "new":
case "create":
- checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_NATION_NEW.getNode());
- newNation(player, split);
+ newNation(player, StringMgmt.remFirstArg(split));
break;
case "join":
checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_NATION_JOIN.getNode());
@@ -567,7 +566,6 @@ public void parseNationCommand(final Player player, String[] split) throws Towny
nationEnemy(player, StringMgmt.remFirstArg(split));
break;
case "delete":
- checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_NATION_DELETE.getNode());
nationDelete(player, StringMgmt.remFirstArg(split));
break;
case "online":
@@ -664,57 +662,6 @@ private void nationEnemyList(Player player, Nation nation) throws TownyException
}
}
- private void parseNationJoin(Player player, String[] args) {
-
- try {
- Resident resident;
- Town town;
- Nation nation;
- String nationName;
-
- if (args.length < 1)
- throw new TownyException(Translatable.of("msg_usage", "/nation join [nation]"));
-
- nationName = args[0];
-
- resident = getResidentOrThrow(player);
- town = resident.getTown();
- nation = getNationOrThrow(nationName);
-
- // Check if town is currently in a nation.
- if (town.hasNation())
- throw new TownyException(Translatable.of("msg_err_already_in_a_nation"));
-
- // Check if town is town is free to join.
- if (!nation.isOpen())
- throw new TownyException(Translatable.of("msg_err_nation_not_open", nation.getFormattedName()));
-
- // Check if the town is sanctioned and not allowed to join.
- if (nation.hasSanctionedTown(town))
- throw new TownyException(Translatable.of("msg_err_cannot_join_nation_sanctioned_town", nation.getName()));
-
- if (!town.hasEnoughResidentsToJoinANation())
- throw new TownyException(Translatable.of("msg_err_not_enough_residents_join_nation", town.getName()));
-
- if (nation.hasReachedMaxTowns())
- throw new TownyException(Translatable.of("msg_err_nation_over_town_limit", TownySettings.getMaxTownsPerNation()));
-
- if (!nation.canAddResidents(town.getNumResidents()))
- throw new TownyException(Translatable.of("msg_err_cannot_join_nation_over_resident_limit", TownySettings.getMaxResidentsPerNation()));
-
- if (TownySettings.getNationProximityToCapital() > 0)
- ProximityUtil.testTownProximityToNation(town, nation); // Throws TownyException with error message when it fails.
-
- // Check if the command is not cancelled
- BukkitTools.ifCancelledThenThrow(new NationPreAddTownEvent(nation, town));
-
- nationAdd(nation, town);
-
- } catch (TownyException e) {
- TownyMessaging.sendErrorMsg(player, e.getMessage(player));
- }
- }
-
private void parseInviteCommand(Player player, String[] newSplit) throws TownyException {
Nation nation = getNationFromPlayerOrThrow(player);
String sent = Translatable.of("nation_sent_invites").forLocale(player)
@@ -916,88 +863,90 @@ public void listNations(CommandSender sender, String[] split) throws TownyExcept
}
private void newNation(Player player, String[] split) throws TownyException {
+ checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_NATION_NEW.getNode());
+ if (split.length == 0)
+ throw new TownyException(Translatable.of("msg_specify_nation_name"));
+
+ String nationName = String.join("_", split);
+ Town town = getTownForNationCapital(player);
+ boolean noCharge = TownySettings.getNewNationPrice() == 0.0 || !TownyEconomyHandler.isActive();
+ newNation((CommandSender) player, nationName, town, noCharge);
+ }
+
+ private Town getTownForNationCapital(Player player) throws TownyException {
Resident resident = getResidentOrThrow(player);
Town town = getTownFromResidentOrThrow(resident);
if (!town.hasEnoughResidentsToBeANationCapital())
throw new TownyException(Translatable.of("msg_err_not_enough_residents_new_nation"));
- if (split.length == 1)
- throw new TownyException(Translatable.of("msg_specify_nation_name"));
-
if (!resident.isMayor() && !town.hasResidentWithRank(resident, "assistant"))
throw new TownyException(Translatable.of("msg_peasant_right"));
-
- boolean noCharge = TownySettings.getNewNationPrice() == 0.0 || !TownyEconomyHandler.isActive();
-
- String nationName = String.join("_", StringMgmt.remFirstArg(split));
- newNation(player, nationName, town, noCharge);
- }
-
- public static void newNation(Player player, String name, Town capitalTown, boolean noCharge) {
- newNation((CommandSender) player, name, capitalTown, noCharge);
+ return town;
}
-
+
/**
- * Create a new nation. Command: /nation new [nation] *[capital]
+ * Ties together the player-run /new nation and the admin-run /ta nation new
+ * NAME CAPITAL code. Vets the name supplied, throws the cancellable event and
+ * then charges (if required) before creating a new nation.
*
- * @param sender Sender who initiated the creation of the nation.
- * @param name Nation name.
- * @param capitalTown Capital city town.
- * @param noCharge charging for creation - /ta nation new NAME CAPITAL has no charge.
+ * @param sender Sender who initiated the creation of the nation.
+ * @param name Nation name to vet.
+ * @param capitalTown Town which will become the capital city.
+ * @param noCharge when true and the Economy is enabled we charge the new
+ * nation cost
*/
- public static void newNation(CommandSender sender, String name, Town capitalTown, boolean noCharge) {
-
- try {
- if (capitalTown.hasNation())
- throw new TownyException(Translatable.of("msg_err_already_nation"));
-
- if (TownySettings.getTownAutomaticCapitalisationEnabled())
- name = StringMgmt.capitalizeStrings(name);
+ public static void newNation(CommandSender sender, String name, Town capitalTown, boolean noCharge) throws TownyException {
- // Check the name is valid and doesn't already exist.
- String filteredName;
- try {
- filteredName = NameValidation.checkAndFilterName(name);
- } catch (InvalidNameException e) {
- filteredName = null;
- }
+ if (capitalTown.hasNation())
+ throw new TownyException(Translatable.of("msg_err_already_nation"));
- if (filteredName == null || TownyUniverse.getInstance().hasNation(filteredName) || (!TownySettings.areNumbersAllowedInNationNames() && NameValidation.containsNumbers(filteredName)))
- throw new TownyException(Translatable.of("msg_err_invalid_name", name));
+ final String filteredName = validateNationNameOrThrow(name);
- BukkitTools.ifCancelledThenThrow(new PreNewNationEvent(capitalTown, filteredName));
+ BukkitTools.ifCancelledThenThrow(new PreNewNationEvent(capitalTown, filteredName));
- // If it isn't free to make a nation, send a confirmation.
- if (!noCharge && TownyEconomyHandler.isActive()) {
- // Test if they can pay.
- double cost = TownySettings.getNewNationPrice();
- if (!capitalTown.getAccount().canPayFromHoldings(cost))
- throw new TownyException(Translatable.of("msg_no_funds_new_nation2", cost));
+ if (noCharge || !TownyEconomyHandler.isActive()) {
+ // It's free so make the nation.
+ Nation nation = newNation(filteredName, capitalTown);
+ TownyMessaging.sendGlobalMessage(Translatable.of("msg_new_nation", sender.getName(), nation.getFormattedName()));
+ return;
+ }
- final String finalName = filteredName;
- Confirmation.runOnAccept(() -> {
- try {
- newNation(finalName, capitalTown);
- } catch (AlreadyRegisteredException | NotRegisteredException e) {
- TownyMessaging.sendErrorMsg(sender, e.getMessage(sender));
- return;
- }
- TownyMessaging.sendGlobalMessage(Translatable.of("msg_new_nation", sender.getName(), StringMgmt.remUnderscore(finalName)));
+ // It isn't free to make a nation, send a confirmation.
+ double cost = TownySettings.getNewNationPrice();
+ // Test if they can pay.
+ if (!capitalTown.getAccount().canPayFromHoldings(cost))
+ throw new TownyException(Translatable.of("msg_no_funds_new_nation2", prettyMoney(cost)));
- })
- .setCost(new ConfirmationTransaction(TownySettings::getNewNationPrice, capitalTown, "New Nation Cost",
- Translatable.of("msg_no_funds_new_nation2", cost)))
- .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
- .sendTo(sender);
-
- // Or, it is free, so just make the nation.
- } else {
- newNation(filteredName, capitalTown);
- TownyMessaging.sendGlobalMessage(Translatable.of("msg_new_nation", sender.getName(), StringMgmt.remUnderscore(filteredName)));
+ Confirmation.runOnAccept(() -> {
+ try {
+ Nation nation = newNation(filteredName, capitalTown);
+ TownyMessaging.sendGlobalMessage(Translatable.of("msg_new_nation", sender.getName(), nation.getFormattedName()));
+ } catch (AlreadyRegisteredException | NotRegisteredException e) {
+ TownyMessaging.sendErrorMsg(sender, e.getMessage(sender));
}
- } catch (TownyException x) {
- TownyMessaging.sendErrorMsg(sender, x.getMessage(sender));
+ })
+ .setCost(new ConfirmationTransaction(TownySettings::getNewNationPrice, capitalTown, "New Nation Cost",
+ Translatable.of("msg_no_funds_new_nation2", prettyMoney(cost))))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
+ .sendTo(sender);
+ }
+
+ private static String validateNationNameOrThrow(String name) throws TownyException {
+ if (TownySettings.getTownAutomaticCapitalisationEnabled())
+ name = StringMgmt.capitalizeStrings(name);
+
+ // Check the name is valid and doesn't already exist.
+ String filteredName;
+ try {
+ filteredName = NameValidation.checkAndFilterName(name);
+ } catch (InvalidNameException e) {
+ filteredName = null;
}
+
+ if (filteredName == null || TownyUniverse.getInstance().hasNation(filteredName) || (!TownySettings.areNumbersAllowedInNationNames() && NameValidation.containsNumbers(filteredName)))
+ throw new TownyException(Translatable.of("msg_err_invalid_name", name));
+
+ return filteredName;
}
public static Nation newNation(String name, Town town) throws AlreadyRegisteredException, NotRegisteredException {
@@ -1012,6 +961,7 @@ public static Nation newNation(String name, Town town) throws AlreadyRegisteredE
TownyMessaging.sendErrorMsg(String.format("Error fetching new nation with name %s; it was not properly registered!", name));
throw new NotRegisteredException(Translatable.of("msg_err_not_registered_1", name));
}
+
nation.setRegistered(System.currentTimeMillis());
nation.setMapColorHexCode(MapUtil.generateRandomNationColourAsHexCode());
town.setNation(nation);
@@ -1022,8 +972,8 @@ public static Nation newNation(String name, Town town) throws AlreadyRegisteredE
nation.getAccount().setBalance(0, "New Nation Account");
if (TownySettings.isNationTagSetAutomatically())
- nation.setTag(name.substring(0, Math.min(name.length(), TownySettings.getMaxTagLength())).replace("_","").replace("-", ""));
-
+ nation.setTag(NameUtil.getTagFromName(name));
+
town.save();
nation.save();
@@ -1086,24 +1036,21 @@ public void nationLeave(Player player) throws TownyException {
BukkitTools.ifCancelledThenThrow(new NationPreTownLeaveEvent(nation, town));
- boolean tooManyResidents = false;
- if (town.isCapital()) {
- // Check that the capital wont have too many residents after deletion.
- tooManyResidents = town.isAllowedThisAmountOfResidents(town.getNumResidents(), false);
- // Show a message preceding the confirmation message if they will lose residents.
- if (tooManyResidents) {
- int maxResidentsPerTown = TownySettings.getMaxResidentsPerTown();
- TownyMessaging.sendMsg(player, Translatable.of("msg_deleting_nation_will_result_in_losing_residents", maxResidentsPerTown, town.getNumResidents() - maxResidentsPerTown));
- }
+ // Check that the capital wont have too many residents after deletion.
+ final boolean tooManyResidents = town.isCapital() && town.isAllowedThisAmountOfResidents(town.getNumResidents(), false);
+ if (tooManyResidents) {
+ // Show a message preceding the confirmation message if they will lose residents.
+ int maxResidentsPerTown = town.getMaxAllowedNumberOfResidentsWithoutNation();
+ TownyMessaging.sendMsg(player, Translatable.of("msg_deleting_nation_will_result_in_losing_residents", maxResidentsPerTown, town.getNumResidents() - maxResidentsPerTown));
}
- final boolean finalTooManyResidents = tooManyResidents;
+
Confirmation.runOnAccept(() -> {
BukkitTools.fireEvent(new NationTownLeaveEvent(nation, town));
town.removeNation();
- if (finalTooManyResidents)
+ if (tooManyResidents)
ResidentUtil.reduceResidentCountToFitTownMaxPop(town);
-
+
plugin.resetCache();
TownyMessaging.sendPrefixedNationMessage(nation, Translatable.of("msg_nation_town_left", StringMgmt.remUnderscore(town.getName())));
@@ -1113,46 +1060,39 @@ public void nationLeave(Player player) throws TownyException {
}).sendTo(player);
}
- public void nationDelete(Player player, String[] split) {
-
+ public void nationDelete(Player player, String[] split) throws TownyException {
// Player is using "/n delete"
if (split.length == 0) {
- try {
- Resident resident = getResidentOrThrow(player);
- Town town = getTownFromResidentOrThrow(resident);
- Nation nation = getNationFromResidentOrThrow(resident);
- // Check that the capital wont have too many residents after deletion.
- boolean tooManyResidents = !town.isAllowedThisAmountOfResidents(town.getNumResidents(), false);
- // Show a message preceding the confirmation message if they will lose residents.
- if (tooManyResidents) {
- int maxResidentsPerTown = TownySettings.getMaxResidentsPerTown();
- TownyMessaging.sendMsg(player, Translatable.of("msg_deleting_nation_will_result_in_losing_residents", maxResidentsPerTown, town.getNumResidents() - maxResidentsPerTown));
- }
+ checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_NATION_DELETE.getNode());
- Confirmation.runOnAccept(() -> {
- TownyMessaging.sendGlobalMessage(Translatable.of("msg_del_nation", nation.getName()));
- TownyUniverse.getInstance().getDataSource().removeNation(nation);
- if (tooManyResidents)
- ResidentUtil.reduceResidentCountToFitTownMaxPop(town);
- })
- .sendTo(player);
- } catch (TownyException x) {
- TownyMessaging.sendErrorMsg(player, x.getMessage(player));
+ Town town = getTownFromPlayerOrThrow(player);
+ Nation nation = getNationFromTownOrThrow(town);
+ // Check that the capital wont have too many residents after deletion.
+ boolean tooManyResidents = !town.isAllowedThisAmountOfResidents(town.getNumResidents(), false);
+ // Show a message preceding the confirmation message if they will lose residents.
+ if (tooManyResidents) {
+ int maxResidentsPerTown = town.getMaxAllowedNumberOfResidentsWithoutNation();
+ TownyMessaging.sendMsg(player, Translatable.of("msg_deleting_nation_will_result_in_losing_residents", maxResidentsPerTown, town.getNumResidents() - maxResidentsPerTown));
}
+
+ Confirmation.runOnAccept(() -> {
+ TownyMessaging.sendGlobalMessage(Translatable.of("msg_del_nation", nation.getName()));
+ TownyUniverse.getInstance().getDataSource().removeNation(nation);
+ if (tooManyResidents)
+ ResidentUtil.reduceResidentCountToFitTownMaxPop(town);
+ })
+ .sendTo(player);
+ return;
+ }
+
// Admin is using "/n delete NATIONNAME"
- } else
- try {
- checkPermOrThrowWithMessage(player, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_NATION_DELETE.getNode(), Translatable.of("msg_err_admin_only_delete_nation"));
+ checkPermOrThrowWithMessage(player, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_NATION_DELETE.getNode(), Translatable.of("msg_err_admin_only_delete_nation"));
- Nation nation = getNationOrThrow(split[0]);
- Confirmation.runOnAccept(() -> {
- TownyMessaging.sendGlobalMessage(Translatable.of("msg_del_nation", nation.getName()));
- TownyUniverse.getInstance().getDataSource().removeNation(nation);
- })
- .sendTo(player);
- } catch (TownyException x) {
- TownyMessaging.sendErrorMsg(player, x.getMessage(player));
- }
+ Nation nation = getNationOrThrow(split[0]);
+ Confirmation.runOnAccept(() -> {
+ TownyMessaging.sendGlobalMessage(Translatable.of("msg_del_nation", nation.getName()));
+ TownyUniverse.getInstance().getDataSource().removeNation(nation);
+ }).sendTo(player);
}
public void nationKing(Player player, String[] split) {
@@ -1161,133 +1101,114 @@ public void nationKing(Player player, String[] split) {
HelpMenu.KING_HELP.send(player);
}
- /**
- * First stage of adding towns to a nation.
- *
- * Tests here are performed to make sure Nation is allowed to add the towns:
- * - make sure the nation hasn't already hit the max towns (if that is required in teh config.)
- *
- * @param player - Player using the command.
- * @param names - Names that will be matched to towns.
- * @throws TownyException generic
- */
- public void nationAdd(Player player, String[] names) throws TownyException {
- if (names.length < 1)
- throw new TownyException("Eg: /nation add [names]");
+ private void parseNationJoin(Player player, String[] args) throws TownyException {
+ if (args.length < 1)
+ throw new TownyException(Translatable.of("msg_usage", "/nation join [nation]"));
- Nation nation = getNationFromPlayerOrThrow(player);
-
- if (nation.hasReachedMaxTowns())
- throw new TownyException(Translatable.of("msg_err_nation_over_town_limit", TownySettings.getMaxTownsPerNation()));
+ Town town = getTownFromPlayerOrThrow(player);
+ Nation nation = getNationOrThrow(args[0]);
- // The list of valid invites.
- List newtownlist = new ArrayList<>();
- // List of invites to be removed.
- List removeinvites = new ArrayList<>();
- for (String townname : new ArrayList<>(Arrays.asList(names))) {
- if (townname.startsWith("-")) {
- // Add them to removing, remove the "-"
- removeinvites.add(townname.substring(1));
- continue;
- }
+ // Check if the nation open able to be joined without an invite.
+ if (!nation.isOpen())
+ throw new TownyException(Translatable.of("msg_err_nation_not_open", nation.getFormattedName()));
- Town town = null;
- try {
- town = getTownOrThrow(townname);
- } catch (NotRegisteredException e) {
- // The Town doesn't actually exist or was mis-spelled.
- removeinvites.add(townname);
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_not_registered_1", townname));
- continue;
- }
+ // Vet whether the town can join the nation.
+ testNationAddTownOrThrow(town, nation);
- if (nation.hasTown(town) || town.hasNation()) {
- // Town is already part of the nation.
- removeinvites.add(townname);
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_already_in_town", townname, town.getNationOrNull().getName()));
- continue;
- }
+ // Actually go through with adding the town.
+ nationAdd(nation, town);
+ }
- if (nation.hasSanctionedTown(town)) {
- // Town is sanctioned and cannot join.
- removeinvites.add(townname);
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_cannot_add_sanctioned_town", townname));
- continue;
- }
+ private void testNationAddTownOrThrow(Town town, Nation nation) throws TownyException {
+ // Check if town is currently in a nation.
+ if (nation.hasTown(town) || town.hasNation())
+ throw new TownyException(Translatable.of("msg_err_already_in_town", town.getName(), town.getNationOrNull().getName()));
- if (!nation.canAddResidents(town.getNumResidents())) {
- // Town has too many residents to join the nation
- removeinvites.add(townname);
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_cannot_join_nation_over_resident_limit", TownySettings.getMaxResidentsPerNation(), townname));
- continue;
- }
+ if (!town.hasEnoughResidentsToJoinANation())
+ throw new TownyException(Translatable.of("msg_err_not_enough_residents_join_nation", town.getName()));
- // add them to adding.
- newtownlist.add(townname);
- }
- names = newtownlist.toArray(new String[0]);
- String[] namestoremove = removeinvites.toArray(new String[0]);
- if (namestoremove.length >= 1) {
- nationRevokeInviteTown(player, nation, TownyAPI.getInstance().getTowns(namestoremove));
- }
+ // Check if the town is sanctioned and not allowed to join.
+ if (nation.hasSanctionedTown(town))
+ throw new TownyException(Translatable.of("msg_err_cannot_join_nation_sanctioned_town", nation.getName()));
- if (names.length >= 1) {
- nationAdd(player, nation, TownyAPI.getInstance().getTowns(names));
- }
+ if (nation.hasReachedMaxTowns())
+ throw new TownyException(Translatable.of("msg_err_nation_over_town_limit", TownySettings.getMaxTownsPerNation()));
+
+ if (!nation.canAddResidents(town.getNumResidents()))
+ throw new TownyException(Translatable.of("msg_err_cannot_join_nation_over_resident_limit", TownySettings.getMaxResidentsPerNation()));
+
+ if (TownySettings.getNationProximityToCapital() > 0)
+ ProximityUtil.testTownProximityToNation(town, nation); // Throws TownyException with error message when it fails.
+
+ // Check if the command is not cancelled
+ BukkitTools.ifCancelledThenThrow(new NationPreAddTownEvent(nation, town));
}
/**
- * Second stage of adding towns to a nation.
- *
- * Tests here are performed to make sure the Towns are allowed to join the Nation:
- * - make sure the town has no nation.
- * - make sure the town has enough residents to join a nation (if it is required in the config.)
- * - make sure the town is close enough to the nation capital (if it is required in the config.)
- *
- * Lastly, invites are sent and if successful, the third stage is called by the invite handler.
+ * First stage of adding towns to a nation. We go through the player-submitted
+ * list of Names, vet them for invite or invite-revocation, then either send
+ * them an invite or revoke their invite when a town name is preceded by "-".
*
- * @param player player sending the request
- * @param nation Nation sending the request
- * @param invited the Town(s) being invited to the Nation
- * @throws TownyException executed when the arraylist (invited) returns empty (no valid town was entered)
+ * @param player Player using the command.
+ * @param names Names that will be matched to towns.
+ * @throws TownyException when no towns were able to be invited.
*/
- public static void nationAdd(Player player, Nation nation, List invited) throws TownyException {
-
- List invitedNames = new ArrayList<>(invited.size());
- for (Town town : invited) {
- if (town.hasNation()) {
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_already_nation"));
- continue;
- }
-
- if (!town.hasEnoughResidentsToJoinANation()) {
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_not_enough_residents_join_nation", town.getName()));
- continue;
- }
+ public void nationAdd(Player player, String[] names) throws TownyException {
- if (TownySettings.getNationProximityToCapital() > 0) {
- try {
- ProximityUtil.testTownProximityToNation(town, nation);
- } catch (TownyException e) {
- TownyMessaging.sendErrorMsg(player, e.getMessage(player));
- continue;
- }
- }
+ if (names.length < 1)
+ throw new TownyException("Eg: /nation add [names]");
- // Check if the command is not cancelled
- BukkitTools.ifCancelledThenThrow(new NationPreAddTownEvent(nation, town));
+ Nation nation = getNationFromPlayerOrThrow(player);
- nationInviteTown(player, nation, town);
- invitedNames.add(town.getName());
- }
+ // Our list of names to scan through.
+ List nameList = new ArrayList<>(Arrays.asList(names));
+
+ // Revoke invites from towns who have already had invites sent.
+ nameList.stream()
+ .filter(name -> name.startsWith("-") || nation.hasTown(name))
+ .map(name -> name.startsWith("-") ? name.substring(1) : name)
+ .filter(name -> TownyAPI.getInstance().getTown(name) != null)
+ .map(name -> TownyAPI.getInstance().getTown(name))
+ .filter(town -> nation.getSentInvites().stream().anyMatch(invite -> town.equals(invite.getReceiver())))
+ .forEach(town -> nationRevokeInviteTown(player, nation, town));
+
+ // Gather a list of Towns able to receive an invite to the nation, sending back
+ // error messages for the towns that cannot, finally dumping them to a list for
+ // the feedback message.
+ List invitedNames = nameList.stream()
+ .filter(name -> !name.startsWith("-"))
+ .filter(name -> TownyAPI.getInstance().getTown(name) != null)
+ .map(name -> TownyAPI.getInstance().getTown(name))
+ .filter(town -> {
+ try {
+ // test that the town can join the nation.
+ testNationAddTownOrThrow(town, nation);
+ // Send the actual invite to the town being added to the nation.
+ nationInviteTown(player, nation, town);
+ } catch (TownyException e) {
+ TownyMessaging.sendErrorMsg(player, e.getMessage(player));
+ return false;
+ }
+ return true;
+ })
+ .map(town -> town.getName())
+ .collect(Collectors.toList());
- if (invitedNames.size() > 0) {
+ // Send the feedback message.
+ if (!invitedNames.isEmpty())
TownyMessaging.sendPrefixedNationMessage(nation, Translatable.of("msg_invited_join_nation", player.getName(), String.join(", ", invitedNames)));
- } else {
- // This is executed when the arraylist returns empty (no valid town was entered).
- throw new TownyException(Translatable.of("msg_invalid_name"));
- }
+ }
+
+ private static void nationRevokeInviteTown(CommandSender sender, Nation nation, Town town) {
+ InviteHandler.getActiveInvitesFor(nation, town).forEach(invite -> {
+ try {
+ InviteHandler.declineInvite(invite, true);
+ TownyMessaging.sendMsg(sender, Translatable.of("nation_revoke_invite_successful"));
+ } catch (InvalidObjectException e) {
+ plugin.getLogger().log(Level.WARNING, "unknown exception occurred while revoking invite", e);
+ }
+ });
}
/**
@@ -1325,6 +1246,13 @@ public static void nationAdd(Nation nation, Town town) {
return;
}
+ if (nation.hasSanctionedTown(town)) {
+ // Nation has sanctioned this town, since inviting them.
+ TownyMessaging.sendPrefixedNationMessage(nation, Translatable.of("msg_err_cannot_add_sanctioned_town", town.getName()));
+ TownyMessaging.sendPrefixedTownMessage(town, Translatable.of("msg_err_cannot_join_nation_sanctioned_town", nation.getName()));
+ return;
+ }
+
if (nation.hasReachedMaxTowns()) {
// Nation has hit the max-towns limit.
TownyMessaging.sendPrefixedNationMessage(nation, Translatable.of("msg_err_nation_over_town_limit", TownySettings.getMaxTownsPerNation()));
@@ -1339,6 +1267,16 @@ public static void nationAdd(Nation nation, Town town) {
return;
}
+ if (TownySettings.getNationProximityToCapital() > 0) {
+ try {
+ ProximityUtil.testTownProximityToNation(town, nation);
+ } catch (TownyException e) {
+ TownyMessaging.sendPrefixedNationMessage(nation, e.getMessage());
+ TownyMessaging.sendPrefixedTownMessage(town, e.getMessage());
+ return;
+ }
+ }
+
try {
town.setNation(nation);
} catch (AlreadyRegisteredException ignored) {}
@@ -1349,25 +1287,6 @@ public static void nationAdd(Nation nation, Town town) {
TownyAPI.getInstance().getOnlinePlayers(town).forEach(p-> plugin.resetCache(p));
}
- private static void nationRevokeInviteTown(CommandSender sender, Nation nation, List towns) {
-
- for (Town town : towns) {
- if (InviteHandler.inviteIsActive(nation, town)) {
- for (Invite invite : town.getReceivedInvites()) {
- if (invite.getSender().equals(nation)) {
- try {
- InviteHandler.declineInvite(invite, true);
- TownyMessaging.sendMsg(sender, Translatable.of("nation_revoke_invite_successful"));
- break;
- } catch (InvalidObjectException e) {
- plugin.getLogger().log(Level.WARNING, "unknown exception occurred while revoking invite", e);
- }
- }
- }
- }
- }
- }
-
private static void nationInviteTown(Player player, Nation nation, Town town) throws TownyException {
TownJoinNationInvite invite = new TownJoinNationInvite(player, town, nation);
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownCommand.java
index a627de8145..b09f2ef4e8 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownCommand.java
@@ -2618,7 +2618,7 @@ public static Town newTown(TownyWorld world, String name, Resident resident, Coo
}
if (TownySettings.isTownTagSetAutomatically())
- town.setTag(name.substring(0, Math.min(name.length(), TownySettings.getMaxTagLength())).replace("_","").replace("-", ""));
+ town.setTag(NameUtil.getTagFromName(name));
resident.save();
townBlock.save();
@@ -3359,6 +3359,10 @@ else if (result.feedback() != null)
// Fast fail when we're claiming a single worldcoord and it is already claimed.
if (selection.size() == 1 && playerWorldCoord.hasTownBlock())
throw new TownyException(Translatable.of("msg_already_claimed", playerWorldCoord.getTownOrNull()));
+
+ // If selection is greater than 1 check for the multiclaim node.
+ if (selection.size() > 1)
+ checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_TOWN_CLAIM_TOWN_MULTIPLE.getNode());
}
if (selection.isEmpty())
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyWorldCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyWorldCommand.java
index a3a0001888..1d9118610c 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyWorldCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyWorldCommand.java
@@ -387,7 +387,9 @@ private void toggleWildernessUse(CommandSender sender, TownyWorld world, String[
TownyMessaging.sendMsg(sender, Translatable.of("msg_wilderness_use_set_to", toggle, world.getName()));
}
- public void worldSet(CommandSender sender, TownyWorld world, String[] split) {
+ public void worldSet(CommandSender sender, TownyWorld world, String[] split) throws NoPermissionException {
+
+ checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYWORLD_SET.getNode());
if (split.length == 0) {
HelpMenu.TOWNYWORLD_SET.send(sender);
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDatabaseHandler.java b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDatabaseHandler.java
index b0c7c8e9e1..d05f9be200 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDatabaseHandler.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDatabaseHandler.java
@@ -690,7 +690,6 @@ public void renameTown(Town town, String newName) throws AlreadyRegisteredExcept
BukkitTools.fireEvent(new RenameTownEvent(oldName, town));
}
- @SuppressWarnings("unlikely-arg-type")
@Override
public void renameNation(Nation nation, String newName) throws AlreadyRegisteredException, NotRegisteredException {
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java
index d9c79136d2..1cfc138040 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java
@@ -294,29 +294,7 @@ public void onPotionSplashEvent(PotionSplashEvent event) {
if (!TownyAPI.getInstance().isTownyWorld(event.getEntity().getWorld()))
return;
- boolean detrimental = false;
-
- /*
- * List of potion effects blocked from PvP.
- */
- List detrimentalPotions = TownySettings.getPotionTypes();
-
- for (PotionEffect effect : event.getPotion().getEffects()) {
-
- /*
- * Check to see if any of the potion effects are protected.
- * TODO: Make up a wrapper of some kind in order to support older versions while
- * using the new methods when possible.
- */
- @SuppressWarnings("deprecation")
- String name = effect.getType().getName();
- if (detrimentalPotions.contains(name)) {
- detrimental = true;
- break;
- }
- }
-
- if (!detrimental)
+ if (!hasDetrimentalEffects(event.getPotion().getEffects()))
return;
for (LivingEntity defender : event.getAffectedEntities()) {
@@ -935,31 +913,26 @@ private boolean entityProtectedFromExplosiveDamageHere(Entity entity, DamageCaus
private boolean hasDetrimentalEffects(Collection effects) {
if (effects.isEmpty())
return false;
-
+
/*
* List of potion effects blocked from PvP.
*/
final List detrimentalPotions = TownySettings.getPotionTypes().stream().map(type -> type.toLowerCase(Locale.ROOT)).collect(Collectors.toList());
- for (final PotionEffect effect : effects) {
- // TODO: Make up a wrapper of some kind in order to support older versions while
- // using the new methods when possible.
- @SuppressWarnings("deprecation")
- final String name = effect.getType().getName().toLowerCase(Locale.ROOT);
+ return effects.stream()
+ .map(effect -> BukkitTools.potionEffectName(effect.getType()))
+ .anyMatch(name -> {
+ // Check to see if any of the potion effects are protected against.
+ if (detrimentalPotions.contains(name))
+ return true;
- /*
- * Check to see if any of the potion effects are protected.
- */
- if (detrimentalPotions.contains(name))
- return true;
-
- // Account for PotionEffect#getType possibly returning the new name post enum removal.
- final String legacyName = POTION_LEGACY_NAMES.inverse().get(name);
- if (legacyName != null && detrimentalPotions.contains(legacyName))
- return true;
- }
-
- return false;
+ // Account for PotionEffect#getType possibly returning the new name post enum removal.
+ final String legacyName = POTION_LEGACY_NAMES.inverse().get(name);
+ if (legacyName != null && detrimentalPotions.contains(legacyName))
+ return true;
+
+ return false;
+ });
}
@ApiStatus.Internal
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityMonitorListener.java b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityMonitorListener.java
index c1be585f74..2cfadd10cf 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityMonitorListener.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyEntityMonitorListener.java
@@ -44,7 +44,6 @@
*/
public class TownyEntityMonitorListener implements Listener {
- @SuppressWarnings("unused")
private final Towny plugin;
public TownyEntityMonitorListener(Towny instance) {
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java
index 9190bd8f3f..5ca6973712 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java
@@ -102,7 +102,6 @@
* @author Shade/ElgarL
*
*/
-@SuppressWarnings("deprecation")
public class TownyPlayerListener implements Listener {
private final Towny plugin;
@@ -246,7 +245,6 @@ public void onPlayerRespawn(PlayerRespawnEvent event) {
}
}
- @SuppressWarnings({"unchecked"})
private boolean isEndPortalRespawn(PlayerRespawnEvent event) {
try {
final Collection> respawnFlags = (Collection>) GET_RESPAWN_FLAGS.invoke(event);
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/AbstractRegistryList.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/AbstractRegistryList.java
index 0651578fec..7524941fec 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/AbstractRegistryList.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/AbstractRegistryList.java
@@ -57,7 +57,6 @@ public Collection tagged() {
return this.tagged;
}
- @SuppressWarnings("unused")
public static class Builder> {
private final Registry registry;
private final Class clazz;
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Town.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Town.java
index 46eb4e7566..3de4f1554a 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Town.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Town.java
@@ -1943,4 +1943,8 @@ public boolean hasEnoughResidentsToBeANationCapital() {
public boolean isAllowedThisAmountOfResidents(int residentCount, boolean isCapital) {
return TownUtil.townCanHaveThisAmountOfResidents(this, residentCount, isCapital);
}
+
+ public int getMaxAllowedNumberOfResidentsWithoutNation() {
+ return TownUtil.getMaxAllowedNumberOfResidentsWithoutNation(this);
+ }
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java b/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java
index 9790300692..894297afb2 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/PermissionNodes.java
@@ -177,6 +177,7 @@ public enum PermissionNodes {
TOWNY_COMMAND_TOWN_CLAIM("towny.command.town.claim.*"),
TOWNY_COMMAND_TOWN_CLAIM_TOWN("towny.command.town.claim.town"),
+ TOWNY_COMMAND_TOWN_CLAIM_TOWN_MULTIPLE("towny.command.town.claim.town.multiple"),
TOWNY_COMMAND_TOWN_CLAIM_OUTPOST("towny.command.town.claim.outpost"),
TOWNY_COMMAND_TOWN_CLAIM_FILL("towny.command.town.claim.fill"),
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/TownyPerms.java b/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/TownyPerms.java
index 0943183ea2..acbe982028 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/TownyPerms.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/permissions/TownyPerms.java
@@ -127,7 +127,6 @@ private static void checkForVitalGroups() {
* @param resident - Resident to check if player is valid
* @param player - Player to register permission
*/
- @SuppressWarnings("unchecked")
public static void assignPermissions(Resident resident, Player player) {
if (resident == null) {
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/ProtectionRegenTask.java b/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/ProtectionRegenTask.java
index 527c1ab537..bbd5beff25 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/ProtectionRegenTask.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/ProtectionRegenTask.java
@@ -152,7 +152,6 @@ public int getTaskId() {
* @deprecated Deprecated as of 0.99.0.6, use {@link #setTask(ScheduledTask)} instead.
*/
@Deprecated
- @SuppressWarnings("unused")
public void setTaskId(int taskId) {
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/NameUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/NameUtil.java
index d9645487cf..2ee563128a 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/NameUtil.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/NameUtil.java
@@ -1,5 +1,6 @@
package com.palmergames.bukkit.towny.utils;
+import com.palmergames.bukkit.towny.TownySettings;
import com.palmergames.bukkit.towny.object.Nameable;
import java.util.ArrayList;
import java.util.Collection;
@@ -49,4 +50,8 @@ public static List filterByStart(List list, String startingWith)
}
return list.stream().filter(name -> name.toLowerCase(Locale.ROOT).startsWith(startingWith.toLowerCase(Locale.ROOT))).collect(Collectors.toList());
}
+
+ public static String getTagFromName(String name) {
+ return name.substring(0, Math.min(name.length(), TownySettings.getMaxTagLength())).replace("_","").replace("-", "");
+ }
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownUtil.java
index 92fbe3cbff..923bf3ebec 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownUtil.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownUtil.java
@@ -96,9 +96,14 @@ public static boolean townHasEnoughResidentsToJoinANation(Town town) {
public static boolean townCanHaveThisAmountOfResidents(Town town, int residentCount, boolean isCapital) {
int maxResidents = !isCapital
- ? !town.hasNation() && TownySettings.getMaxNumResidentsWithoutNation() > 0 ? TownySettings.getMaxNumResidentsWithoutNation() : TownySettings.getMaxResidentsPerTown()
+ ? !town.hasNation() ? getMaxAllowedNumberOfResidentsWithoutNation(town) : TownySettings.getMaxResidentsPerTown()
: TownySettings.getMaxResidentsPerTownCapitalOverride();
return maxResidents == 0 || residentCount <= maxResidents;
}
+
+ public static int getMaxAllowedNumberOfResidentsWithoutNation(Town town) {
+ int maxResidents = TownySettings.getMaxNumResidentsWithoutNation() > 0 ? TownySettings.getMaxNumResidentsWithoutNation() : TownySettings.getMaxResidentsPerTown();
+ return maxResidents == 0 ? Integer.MAX_VALUE : maxResidents;
+ }
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/util/BukkitTools.java b/Towny/src/main/java/com/palmergames/bukkit/util/BukkitTools.java
index ff59264ae1..fce5b0600b 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/util/BukkitTools.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/util/BukkitTools.java
@@ -24,6 +24,7 @@
import org.bukkit.event.Event;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.PluginManager;
+import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scoreboard.Criteria;
import org.bukkit.scoreboard.Objective;
@@ -328,6 +329,14 @@ public static List getWorldNames(boolean lowercased) {
return lowercased ? getWorlds().stream().map(world -> world.getName().toLowerCase(Locale.ROOT)).collect(Collectors.toList()) : getWorldNames();
}
+ @SuppressWarnings("deprecation")
+ public static String potionEffectName(PotionEffectType type) {
+ if (MinecraftVersion.CURRENT_VERSION.isOlderThanOrEquals(MinecraftVersion.MINECRAFT_1_20_3))
+ return type.getName().toLowerCase(Locale.ROOT);
+ else
+ return type.getKey().getKey().toLowerCase(Locale.ROOT);
+ }
+
@SuppressWarnings("deprecation")
public static Objective objective(Scoreboard board, @NotNull String name, @NotNull String displayName) {
Objective objective;
diff --git a/Towny/src/main/resources/ChangeLog.txt b/Towny/src/main/resources/ChangeLog.txt
index b49d0b2040..0d33b2535c 100644
--- a/Towny/src/main/resources/ChangeLog.txt
+++ b/Towny/src/main/resources/ChangeLog.txt
@@ -9469,4 +9469,12 @@ v0.92.0.11:
- Refactor max residents per town code into TownUtil accessed via a new Town method.
- Bump Spigot 1.20.2 to 1.20.4.
- Bump com.github.seeseemelk:MockBukkit-v1.20 from 3.60.0 to 3.65.0.
- - Bump org.apache.maven.plugins:maven-surefire-plugin from 3.2.3 to 3.2.5.
\ No newline at end of file
+ - Bump org.apache.maven.plugins:maven-surefire-plugin from 3.2.3 to 3.2.5.
+ - Re-add the towny.command.town.claim.town.multiple permission node.
+ - A child node of towny.command.town.claim.*.
+ - Negate this node to stop towns claiming multiple plots at once, ie: with /t claim auto.
+ - Refactor parts of the NationCommand class, cleaning up edge case scenarios when towns might join/create nations when they shouldn't.
+0.100.1.5:
+ - Remove unneeded annotations.
+ - Fix permission regression from 0.100.1.1.
+ - Add a wrapper for getting potion names post-1.20.4.
\ No newline at end of file
diff --git a/Towny/src/main/resources/lang/uk-UA.yml b/Towny/src/main/resources/lang/uk-UA.yml
index c369b24f88..cb23c4e267 100644
--- a/Towny/src/main/resources/lang/uk-UA.yml
+++ b/Towny/src/main/resources/lang/uk-UA.yml
@@ -59,8 +59,8 @@ towny_help_4: "Показати час до початку нового дня"
towny_help_5: "Показати статистику"
towny_help_6: "Показати версію Towny"
towny_top_help_0: "List top residents based on subcommand."
-towny_top_help_1: "List top land-owners based on subcommand."
-towny_top_help_2: "List wealthiest based on subcommand."
+towny_top_help_1: "Топ землевласників на основі субкоманди."
+towny_top_help_2: "Список найбагатших на основі субкоманди."
town_help_1: 'Статус вашого міста'
town_help_2: '[Мер]'
town_help_3: 'Вибраний статус міста'
@@ -95,8 +95,8 @@ town_help_33: "Використайте /t buy ? для допомоги."
town_help_34: "Використайте /t toggle ? для допомоги."
town_help_35: "Використовуйте /t rank ? для допомоги."
town_list_help_0: "Переглянути вказану сторінку."
-town_list_help_1: "List towns by resident-count, with the specified page."
-town_list_help_2: "List towns which are open, with the specified page."
+town_list_help_1: "Список міст за кількістю жителів із зазначеною сторінкою."
+town_list_help_2: "Список міст, які відкриті, із зазначеною сторінкою."
town_list_help_3: "List towns using bank balance, with the specified page."
town_list_help_4: "List towns in alphabetical order, with the specified page."
town_list_help_5: "List towns by claimed land, with the specified page."
diff --git a/Towny/src/main/resources/plugin.yml b/Towny/src/main/resources/plugin.yml
index aa437458e6..ffdb1fc0a1 100644
--- a/Towny/src/main/resources/plugin.yml
+++ b/Towny/src/main/resources/plugin.yml
@@ -423,6 +423,7 @@ permissions:
children:
towny.command.town.claim.town: true
towny.command.town.claim.outpost: true
+ towny.command.town.claim.town.multiple: true
towny.command.town.claim.fill: true
towny.command.town.invite.*: