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.*: