diff --git a/Towny/pom.xml b/Towny/pom.xml
index a6316a314c..220d5e400a 100644
--- a/Towny/pom.xml
+++ b/Towny/pom.xml
@@ -13,7 +13,7 @@
towny
jar
- 0.100.0.11
+ 0.100.0.14
@@ -167,7 +167,7 @@
net.kyori
adventure-platform-bukkit
- 4.3.1
+ 4.3.2
net.kyori
@@ -256,7 +256,7 @@
com.github.seeseemelk
MockBukkit-v1.20
- 3.56.0
+ 3.58.0
test
@@ -472,7 +472,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.2.2
+ 3.2.3
${skipTests}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java
index 438870324d..5956c3ab8c 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyEconomyHandler.java
@@ -285,6 +285,8 @@ public static boolean setBalance(String accountName, double amount, World world)
* @return string containing the formatted balance
*/
public static String getFormattedBalance(double balance) {
+ if (!isActive())
+ return String.valueOf(balance);
String formattedBalance = economy.getFormattedBalance(balance);
if (formattedBalance != null) {
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/BaseCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/BaseCommand.java
index 393b05d30c..b46d0c15d0 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/BaseCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/BaseCommand.java
@@ -1,6 +1,7 @@
package com.palmergames.bukkit.towny.command;
import com.palmergames.bukkit.towny.TownyAPI;
+import com.palmergames.bukkit.towny.TownyEconomyHandler;
import com.palmergames.bukkit.towny.TownyUniverse;
import com.palmergames.bukkit.towny.exceptions.NoPermissionException;
import com.palmergames.bukkit.towny.exceptions.ResidentNPCException;
@@ -390,4 +391,8 @@ public static void catchNPCResident(Resident resident) throws ResidentNPCExcepti
if (resident.isNPC())
throw new ResidentNPCException();
}
+
+ public static String prettyMoney(double cost) {
+ return TownyEconomyHandler.getFormattedBalance(cost);
+ }
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java
index 128c0c6ddd..34f710ef39 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/HelpMenu.java
@@ -937,6 +937,17 @@ protected MenuBuilder load() {
}
},
+ NATION_SANCTIONTOWN {
+ @Override
+ protected MenuBuilder load() {
+ return new MenuBuilder("nation sanctiontown")
+ .add("add [town]", Translatable.of("nation_sanction_help_1"))
+ .add("remove [town]", Translatable.of("nation_sanction_help_2"))
+ .add("list", Translatable.of("nation_sanction_help_3"))
+ .add("list [nation]", Translatable.of("nation_sanction_help_4"));
+ }
+ },
+
ALLIES_STRING {
@Override
protected MenuBuilder load() {
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 94b7a37309..aee7927a0f 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
@@ -17,6 +17,8 @@
import com.palmergames.bukkit.towny.event.NationPreRemoveEnemyEvent;
import com.palmergames.bukkit.towny.event.nation.NationRankAddEvent;
import com.palmergames.bukkit.towny.event.nation.NationRankRemoveEvent;
+import com.palmergames.bukkit.towny.event.nation.NationSanctionTownAddEvent;
+import com.palmergames.bukkit.towny.event.nation.NationSanctionTownRemoveEvent;
import com.palmergames.bukkit.towny.event.nation.NationSetSpawnEvent;
import com.palmergames.bukkit.towny.event.nation.NationTownLeaveEvent;
import com.palmergames.bukkit.towny.event.NationRemoveEnemyEvent;
@@ -125,6 +127,7 @@ public class NationCommand extends BaseCommand implements CommandExecutor {
"enemylist",
"ally",
"spawn",
+ "sanctiontown",
"king",
"leader",
"bankhistory",
@@ -235,6 +238,18 @@ else if (args.length == 3)
if (args.length == 3) {
return Collections.singletonList("-ignore");
}
+ case "sanctiontown":
+ if (args.length == 2)
+ return NameUtil.filterByStart(Arrays.asList("add", "remove", "list"), args[1]);
+ if (args.length == 3 && args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove"))
+ return NameUtil.filterByStart(TownyUniverse.getInstance().getTowns()
+ .stream()
+ .filter(t -> !nation.hasTown(t))
+ .map(Town::getName)
+ .collect(Collectors.toList()), args[2]);
+ if (args.length == 3 && args[1].equalsIgnoreCase("list"))
+ return getTownyStartingWith(args[2], "n");
+ break;
case "add":
return getTownyStartingWith(args[args.length - 1], "t");
case "kick":
@@ -528,6 +543,9 @@ public void parseNationCommand(final Player player, String[] split) throws Towny
checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_NATION_KICK.getNode());
nationKick(player, StringMgmt.remFirstArg(split));
break;
+ case "sanctiontown":
+ nationSanctionTown(player, null, StringMgmt.remFirstArg(split));
+ break;
case "set":
/* Permission test is internal*/
nationSet(player, StringMgmt.remFirstArg(split), false, null);
@@ -666,7 +684,11 @@ private void parseNationJoin(Player player, String[] args) {
// 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 (!testTownHasEnoughResidents(town))
throw new TownyException(Translatable.of("msg_err_not_enough_residents_join_nation", town.getName()));
@@ -945,8 +967,9 @@ public static void newNation(CommandSender sender, String name, Town capitalTown
// If it isn't free to make a nation, send a confirmation.
if (!noCharge && TownyEconomyHandler.isActive()) {
// Test if they can pay.
- if (!capitalTown.getAccount().canPayFromHoldings(TownySettings.getNewNationPrice()))
- throw new TownyException(Translatable.of("msg_no_funds_new_nation2", TownySettings.getNewNationPrice()));
+ double cost = TownySettings.getNewNationPrice();
+ if (!capitalTown.getAccount().canPayFromHoldings(cost))
+ throw new TownyException(Translatable.of("msg_no_funds_new_nation2", cost));
final String finalName = filteredName;
Confirmation.runOnAccept(() -> {
@@ -959,9 +982,9 @@ public static void newNation(CommandSender sender, String name, Town capitalTown
TownyMessaging.sendGlobalMessage(Translatable.of("msg_new_nation", sender.getName(), StringMgmt.remUnderscore(finalName)));
})
- .setCost(new ConfirmationTransaction(TownySettings::getNewNationPrice, capitalTown.getAccount(), "New Nation Cost",
- Translatable.of("msg_no_funds_new_nation2", TownySettings.getNewNationPrice())))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(TownySettings.getNewNationPrice())))
+ .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.
@@ -1179,6 +1202,13 @@ public void nationAdd(Player player, String[] names) throws TownyException {
continue;
}
+ 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;
+ }
+
if (!testNationMaxResidents(nation, town)) {
// Town has too many residents to join the nation
removeinvites.add(townname);
@@ -1397,6 +1427,73 @@ public static void nationKick(CommandSender sender, Nation nation, List ki
TownyMessaging.sendErrorMsg(sender, Translatable.of("msg_invalid_name"));
}
+ public static void nationSanctionTown(CommandSender sender, Nation nation, String[] args) throws TownyException {
+ if (args.length == 0 || args[0].equals("?")) {
+ HelpMenu.NATION_SANCTIONTOWN.send(sender);
+ return;
+ }
+
+ if (nation == null && sender instanceof Player player)
+ nation = getNationFromPlayerOrThrow(player);
+
+ if (nation == null)
+ throw new TownyException(Translatable.of("msg_err_no_nation_cannot_do"));
+
+ if (args[0].toLowerCase(Locale.ROOT).equals("list")) {
+ if (args.length == 2)
+ nation = getNationOrThrow(args[1]);
+ nationSanctionTownList(sender, nation);
+ return;
+ }
+
+ if (args.length != 2) {
+ HelpMenu.NATION_SANCTIONTOWN.send(sender);
+ return;
+ }
+ checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_NATION_SANCTIONTOWN.getNode());
+ Town town = getTownOrThrow(args[1]);
+ switch(args[0].toLowerCase(Locale.ROOT)) {
+ case "add" -> nationSanctionTownAdd(sender, nation, town);
+ case "remove" -> nationSactionTownRemove(sender, nation, town);
+ default -> HelpMenu.NATION_SANCTIONTOWN.send(sender);
+ }
+ }
+
+ private static void nationSanctionTownList(CommandSender sender, Nation nation) {
+ if (nation.getSanctionedTowns().isEmpty()) {
+ TownyMessaging.sendMsg(sender, Translatable.of("msg_err_nation_has_no_sanctioned_towns"));
+ return;
+ }
+ Translator translator = Translator.locale(sender);
+ TownyMessaging.sendMessage(sender, ChatTools.formatTitle(nation.getName() + " " + translator.of("title_nation_sanctioned_towns")));
+ TownyMessaging.sendMessage(sender, TownyFormatter.getFormattedTownyObjects(translator.of("title_nation_sanctioned_towns"), new ArrayList<>(nation.getSanctionedTowns())));
+ }
+
+ private static void nationSanctionTownAdd(CommandSender sender, Nation nation, Town town) throws TownyException {
+ if (nation.hasTown(town))
+ throw new TownyException(Translatable.of("msg_err_nation_cannot_sanction_own_town"));
+
+ if (nation.hasSanctionedTown(town))
+ throw new TownyException(Translatable.of("msg_err_nation_town_already_sanctioned"));
+
+ BukkitTools.ifCancelledThenThrow(new NationSanctionTownAddEvent(nation, town));
+
+ nation.addSanctionedTown(town);
+ nation.save();
+ TownyMessaging.sendMsg(sender, Translatable.of("msg_err_nation_town_sanctioned", town.getName()));
+ }
+
+ private static void nationSactionTownRemove(CommandSender sender, Nation nation, Town town) throws TownyException {
+ if (!nation.hasSanctionedTown(town))
+ throw new TownyException(Translatable.of("msg_err_nation_town_isnt_sanctioned"));
+
+ BukkitTools.ifCancelledThenThrow(new NationSanctionTownRemoveEvent(nation, town));
+
+ nation.removeSanctionedTown(town);
+ nation.save();
+ TownyMessaging.sendMsg(sender, Translatable.of("msg_err_nation_town_unsanctioned", town.getName()));
+ }
+
private void nationAlly(Player player, String[] split) throws TownyException {
if (split.length == 0) {
HelpMenu.ALLIES_STRING.send(player);
@@ -1973,11 +2070,12 @@ private static void nationSetMapColor(CommandSender sender, Nation nation, Strin
if (!TownySettings.getNationColorsMap().containsKey(color))
throw new TownyException(Translatable.of("msg_err_invalid_nation_map_color", TownySettings.getNationColorsMap().keySet().toString()));
- if (TownySettings.getNationSetMapColourCost() > 0)
+ double cost = TownySettings.getNationSetMapColourCost();
+ if (cost > 0)
Confirmation
.runOnAccept(() -> setNationMapColor(nation, color, admin, sender))
- .setTitle(Translatable.of("msg_confirm_purchase", TownySettings.getNationSetMapColourCost()))
- .setCost(new ConfirmationTransaction(()-> TownySettings.getNationSetMapColourCost(), nation.getAccount(), "Cost of setting nation map color."))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
+ .setCost(new ConfirmationTransaction(() -> cost, nation, "Cost of setting nation map color."))
.sendTo(sender);
else
setNationMapColor(nation, color, admin, sender);
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/PlotCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/PlotCommand.java
index a67ce9d134..c040373a89 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/PlotCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/PlotCommand.java
@@ -588,12 +588,12 @@ private void tryPlotSetType(Player player, Resident resident, TownBlock townBloc
// Test if we can pay first to throw an exception.
if (cost > 0 && TownyEconomyHandler.isActive() && !resident.getAccount().canPayFromHoldings(cost))
- throw new TownyException(Translatable.of("msg_err_cannot_afford_plot_set_type_cost", townBlockType, TownyEconomyHandler.getFormattedBalance(cost)));
+ throw new TownyException(Translatable.of("msg_err_cannot_afford_plot_set_type_cost", townBlockType, prettyMoney(cost)));
// Handle payment via a confirmation to avoid suprise costs.
if (cost > 0 && TownyEconomyHandler.isActive()) {
Confirmation.runOnAccept(() -> {
- TownyMessaging.sendMsg(resident, Translatable.of("msg_plot_set_cost", TownyEconomyHandler.getFormattedBalance(cost), townBlockType));
+ TownyMessaging.sendMsg(resident, Translatable.of("msg_plot_set_cost", prettyMoney(cost), townBlockType));
try {
townBlock.setType(townBlockType, resident);
@@ -604,9 +604,9 @@ private void tryPlotSetType(Player player, Resident resident, TownBlock townBloc
BukkitTools.fireEvent(new PlayerChangePlotTypeEvent(townBlockType, oldType, townBlock, player));
TownyMessaging.sendMsg(player, Translatable.of("msg_plot_set_type", townBlockType));
})
- .setCost(new ConfirmationTransaction(() -> cost, resident.getAccount(), String.format("Plot set to %s", townBlockType),
- Translatable.of("msg_err_cannot_afford_plot_set_type_cost", townBlockType, TownyEconomyHandler.getFormattedBalance(cost))))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(cost)))
+ .setCost(new ConfirmationTransaction(() -> cost, resident, String.format("Plot set to %s", townBlockType),
+ Translatable.of("msg_err_cannot_afford_plot_set_type_cost", townBlockType, prettyMoney(cost))))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
.sendTo(BukkitTools.getPlayerExact(resident.getName()));
// No cost or economy so no confirmation.
@@ -648,20 +648,20 @@ public void parsePlotSetOutpost(Player player, Resident resident, TownBlock town
// Throws a TownyException with message if outpost should not be set.
OutpostUtil.OutpostTests(town, resident, townyWorld, key, resident.isAdmin(), true);
- if (TownyEconomyHandler.isActive() && TownySettings.getOutpostCost() > 0) {
+ if (TownySettings.getOutpostCost() > 0) {
// Create a confirmation for setting outpost.
Confirmation.runOnAccept(() -> {
// Set the outpost spawn and display feedback.
town.addOutpostSpawn(player.getLocation());
- TownyMessaging.sendMsg(player, Translatable.of("msg_plot_set_cost", TownyEconomyHandler.getFormattedBalance(TownySettings.getOutpostCost()), Translatable.of("outpost")));
+ TownyMessaging.sendMsg(player, Translatable.of("msg_plot_set_cost", prettyMoney(TownySettings.getOutpostCost()), Translatable.of("outpost")));
})
- .setCost(new ConfirmationTransaction(() -> TownySettings.getOutpostCost(), town.getAccount(), "PlotSetOutpost", Translatable.of("msg_err_cannot_afford_to_set_outpost")))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(TownySettings.getOutpostCost())))
+ .setCost(new ConfirmationTransaction(() -> TownySettings.getOutpostCost(), town, "PlotSetOutpost", Translatable.of("msg_err_cannot_afford_to_set_outpost")))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(TownySettings.getOutpostCost())))
.sendTo(player);
} else {
// Set the outpost spawn and display feedback with no cost confirmation.
town.addOutpostSpawn(player.getLocation());
- TownyMessaging.sendMsg(player, Translatable.of("msg_plot_set_cost", TownyEconomyHandler.getFormattedBalance(TownySettings.getOutpostCost()), Translatable.of("outpost")));
+ TownyMessaging.sendMsg(player, Translatable.of("msg_plot_set_cost", prettyMoney(TownySettings.getOutpostCost()), Translatable.of("outpost")));
}
}
@@ -994,7 +994,7 @@ public void setPlotForSale(Resident resident, WorldCoord worldCoord, double forS
if (forSale != -1) {
Translatable message = TownyEconomyHandler.isActive()
- ? Translatable.of("msg_plot_for_sale_amount", resident.getName(), worldCoord.toString(), TownyEconomyHandler.getFormattedBalance(townBlock.getPlotPrice()))
+ ? Translatable.of("msg_plot_for_sale_amount", resident.getName(), worldCoord.toString(), prettyMoney(townBlock.getPlotPrice()))
: Translatable.of("msg_plot_for_sale", resident.getName(), worldCoord.toString());
TownyMessaging.sendPrefixedTownMessage(townBlock.getTownOrNull(), message);
@@ -1342,7 +1342,7 @@ public void parsePlotGroupForSale(String[] split, Resident resident, TownBlock t
group.save();
Translatable message = TownyEconomyHandler.isActive()
- ? Translatable.of("msg_player_put_group_up_for_sale_amount", player.getName(), group.getName(), TownyEconomyHandler.getFormattedBalance(group.getPrice()))
+ ? Translatable.of("msg_player_put_group_up_for_sale_amount", player.getName(), group.getName(), prettyMoney(group.getPrice()))
: Translatable.of("msg_player_put_group_up_for_sale", player.getName(), group.getName());
TownyMessaging.sendPrefixedTownMessage(town, message);
@@ -1576,7 +1576,7 @@ public void parsePlotGroupSetTownBlockType(String[] split, Resident resident, To
double cost = type.getCost() * plotGroupTownBlocks.size();
// Test if we can pay first to throw an exception.
if (cost > 0 && TownyEconomyHandler.isActive() && !resident.getAccount().canPayFromHoldings(cost))
- throw new TownyException(Translatable.of("msg_err_cannot_afford_plot_set_type_cost", type, TownyEconomyHandler.getFormattedBalance(cost)));
+ throw new TownyException(Translatable.of("msg_err_cannot_afford_plot_set_type_cost", type, prettyMoney(cost)));
// Handle payment via a confirmation to avoid suprise costs.
if (cost > 0 && TownyEconomyHandler.isActive()) {
@@ -1584,7 +1584,7 @@ public void parsePlotGroupSetTownBlockType(String[] split, Resident resident, To
if (townBlock.getPlotObjectGroup() == null)
return;
- TownyMessaging.sendMsg(resident, Translatable.of("msg_plot_set_cost", TownyEconomyHandler.getFormattedBalance(cost), type));
+ TownyMessaging.sendMsg(resident, Translatable.of("msg_plot_set_cost", prettyMoney(cost), type));
for (TownBlock tb : townBlock.getPlotObjectGroup().getTownBlocks()) {
try {
@@ -1597,11 +1597,9 @@ public void parsePlotGroupSetTownBlockType(String[] split, Resident resident, To
}
TownyMessaging.sendMsg(player, Translatable.of("msg_set_group_type_to_x", type));
})
- .setCost(new ConfirmationTransaction(() -> type.getCost() * plotGroupTownBlocks.size(),
- resident.getAccount(),
- String.format("Plot group (" + plotGroupTownBlocks.size() + ") set to %s", type),
- Translatable.of("msg_err_cannot_afford_plot_set_type_cost", type, TownyEconomyHandler.getFormattedBalance(cost))))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(cost)))
+ .setCost(new ConfirmationTransaction(() -> cost, resident, String.format("Plot group (%s) set to %s", plotGroupTownBlocks.size(), type),
+ Translatable.of("msg_err_cannot_afford_plot_set_type_cost", type, prettyMoney(cost))))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
.sendTo(BukkitTools.getPlayerExact(resident.getName()));
// No cost or economy so no confirmation.
} else {
@@ -1837,7 +1835,7 @@ private void continuePlotClaimProcess(List selection, Resident resid
PlotGroup group = tb.getPlotObjectGroup();
if (TownyEconomyHandler.isActive() && (!resident.getAccount().canPayFromHoldings(group.getPrice())))
- throw new TownyException(Translatable.of("msg_no_funds_claim_plot_group", group.getTownBlocks().size(), TownyEconomyHandler.getFormattedBalance(group.getPrice())));
+ throw new TownyException(Translatable.of("msg_no_funds_claim_plot_group", group.getTownBlocks().size(), prettyMoney(group.getPrice())));
// Add the confirmation for claiming a plot group.
Confirmation.runOnAccept(() -> {
@@ -1849,7 +1847,7 @@ private void continuePlotClaimProcess(List selection, Resident resid
// Execute the plot claim.
new PlotClaim(Towny.getPlugin(), player, resident, coords, true, false, true).start();
})
- .setTitle(Translatable.of("msg_plot_group_claim_confirmation", group.getTownBlocks().size()).append(" ").append(TownyEconomyHandler.getFormattedBalance(group.getPrice())).append(". ").append(Translatable.of("are_you_sure_you_want_to_continue")))
+ .setTitle(Translatable.of("msg_plot_group_claim_confirmation", group.getTownBlocks().size()).append(" ").append(prettyMoney(group.getPrice())).append(". ").append(Translatable.of("are_you_sure_you_want_to_continue")))
.sendTo(player);
return;
@@ -1877,7 +1875,7 @@ private void continuePlotClaimProcess(List selection, Resident resid
throw new TownyException(Translatable.of("msg_max_plot_own", maxPlots));
if (TownyEconomyHandler.isActive() && (!resident.getAccount().canPayFromHoldings(cost)))
- throw new TownyException(Translatable.of("msg_no_funds_claim_plot", TownyEconomyHandler.getFormattedBalance(cost)));
+ throw new TownyException(Translatable.of("msg_no_funds_claim_plot", prettyMoney(cost)));
if (cost != 0) {
final List finalSelection = selection;
@@ -1885,7 +1883,7 @@ private void continuePlotClaimProcess(List selection, Resident resid
// Start the claim task
new PlotClaim(plugin, player, resident, finalSelection, true, false, false).start();
})
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(cost)))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
.sendTo(player);
} else {
// Start the claim task
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/ResidentCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/ResidentCommand.java
index 88e191a5ee..ffad445da4 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/ResidentCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/ResidentCommand.java
@@ -389,7 +389,7 @@ private void parseResidentJail(Player player, String[] split) throws TownyExcept
TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_unable_to_pay_bail"));
}
})
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(cost)))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
.sendTo(player);
}
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 1f1ec4efbb..e92976ff3a 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
@@ -1042,7 +1042,7 @@ private void townPlots(CommandSender sender, String[] args) throws TownyExceptio
String plotTypeLine = translator.of("msg_town_plots_type_line", type.getFormattedName(), residentOwned,
typeCache.getNumTownBlocks(type, CacheType.FORSALE), typeCache.getNumTownBlocks(type, CacheType.ALL));
if (TownyEconomyHandler.isActive())
- plotTypeLine += translator.of("msg_town_plots_type_line_revenue", TownyEconomyHandler.getFormattedBalance(residentOwned * type.getTax(town)));
+ plotTypeLine += translator.of("msg_town_plots_type_line_revenue", prettyMoney(residentOwned * type.getTax(town)));
out.add(plotTypeLine);
}
out.add(Translatable.of("msg_town_plots_revenue_disclaimer").forLocale(player));
@@ -1175,7 +1175,7 @@ public void listTowns(CommandSender sender, String[] split) throws TownyExceptio
Translatable spawnCost = Translatable.of("msg_spawn_cost_free");
if (TownyEconomyHandler.isActive())
- spawnCost = Translatable.of("msg_spawn_cost", TownyEconomyHandler.getFormattedBalance(town.getSpawnCost()));
+ spawnCost = Translatable.of("msg_spawn_cost", prettyMoney(town.getSpawnCost()));
townName = townName.hoverEvent(HoverEvent.showText(Translatable.of("msg_click_spawn", town).append("\n").append(spawnCost).locale(sender).component()));
output.add(Pair.pair(town.getUUID(), townName));
@@ -1419,7 +1419,7 @@ private static void townToggleNeutral(CommandSender sender, boolean admin, Town
// If they setting neutral status on send a message confirming they paid something, if they did.
if (peacefulState && TownyEconomyHandler.isActive() && cost > 0) {
town.getAccount().withdraw(cost, "Peaceful Town Cost");
- TownyMessaging.sendMsg(sender, Translatable.of("msg_you_paid", TownyEconomyHandler.getFormattedBalance(cost)));
+ TownyMessaging.sendMsg(sender, Translatable.of("msg_you_paid", prettyMoney(cost)));
}
// Set the toggle setting.
@@ -2258,12 +2258,12 @@ public static void townSetName(CommandSender sender, String[] split, Town town)
if(TownyEconomyHandler.isActive() && TownySettings.getTownRenameCost() > 0) {
if (!town.getAccount().canPayFromHoldings(TownySettings.getTownRenameCost()))
- throw new TownyException(Translatable.of("msg_err_no_money", TownyEconomyHandler.getFormattedBalance(TownySettings.getTownRenameCost())));
+ throw new TownyException(Translatable.of("msg_err_no_money", prettyMoney(TownySettings.getTownRenameCost())));
final Town finalTown = town;
final String finalName = name;
Confirmation.runOnAccept(() -> townRename(sender, finalTown, finalName))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(TownySettings.getTownRenameCost())))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(TownySettings.getTownRenameCost())))
.sendTo(sender);
} else {
townRename(sender, town, name);
@@ -2413,8 +2413,8 @@ public static void townSetMapColor(CommandSender sender, String[] split, Town to
if (TownySettings.getTownSetMapColourCost() > 0)
Confirmation
.runOnAccept(()-> setTownMapColor(town, color))
- .setTitle(Translatable.of("msg_confirm_purchase", TownySettings.getTownSetMapColourCost()))
- .setCost(new ConfirmationTransaction(()-> TownySettings.getTownSetMapColourCost(), town.getAccount(), "Cost of setting town map color."))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(TownySettings.getTownSetMapColourCost())))
+ .setCost(new ConfirmationTransaction(TownySettings::getTownSetMapColourCost, town, "Cost of setting town map color."))
.sendTo(sender);
else
setTownMapColor(town, color);
@@ -2435,7 +2435,7 @@ public static void townSetTaxPercent(CommandSender sender, String[] split, Town
town.setMaxPercentTaxAmount(Double.parseDouble(split[1]));
- TownyMessaging.sendPrefixedTownMessage(town, Translatable.of("msg_town_set_tax_max_percent_amount", sender.getName(), TownyEconomyHandler.getFormattedBalance(town.getMaxPercentTaxAmount())));
+ TownyMessaging.sendPrefixedTownMessage(town, Translatable.of("msg_town_set_tax_max_percent_amount", sender.getName(), prettyMoney(town.getMaxPercentTaxAmount())));
}
private static void parseTownBaltop(Player player, Town town) throws TownyException {
@@ -2447,7 +2447,7 @@ private static void parseTownBaltop(Player player, Town town) throws TownyExcept
int i = 0;
for (Resident res : residents)
- sb.append(Translatable.of("msg_baltop_book_format", ++i, res.getName(), TownyEconomyHandler.getFormattedBalance(res.getAccount().getCachedBalance())).forLocale(player) + "\n");
+ sb.append(Translatable.of("msg_baltop_book_format", ++i, res.getName(), prettyMoney(res.getAccount().getCachedBalance())).forLocale(player) + "\n");
ItemStack book = BookFactory.makeBook("Town Baltop", town.getName(), sb.toString());
plugin.getScheduler().run(player, () -> player.openBook(book));
@@ -2472,7 +2472,7 @@ else if (TownySettings.isBonusBlocksPerTownLevel() && TownySettings.getMaxBonusB
if (split.length == 0 || !split[0].equalsIgnoreCase("bonus")) {
TownyMessaging.sendMessage(sender, ChatTools.formatTitle("/town buy"));
String line = Colors.Yellow + "[Purchased Bonus] " + Colors.Green + "Cost: " + Colors.LightGreen + "%s" + Colors.Gray + " | " + Colors.Green + "Max: " + Colors.LightGreen + "%d";
- TownyMessaging.sendMessage(sender, String.format(line, TownyEconomyHandler.getFormattedBalance(town.getBonusBlockCost()), TownySettings.getMaxPurchasedBlocks(town)));
+ TownyMessaging.sendMessage(sender, String.format(line, prettyMoney(town.getBonusBlockCost()), TownySettings.getMaxPurchasedBlocks(town)));
if (TownySettings.getPurchasedBonusBlocksIncreaseValue() != 1.0)
TownyMessaging.sendMessage(sender, Colors.Green + "Cost Increase per TownBlock: " + Colors.LightGreen + "+" + new DecimalFormat("##.##%").format(TownySettings.getPurchasedBonusBlocksIncreaseValue()-1));
TownyMessaging.sendMessage(sender, ChatTools.formatCommand("", "/town buy", "bonus [n]", ""));
@@ -2514,16 +2514,16 @@ public static void townBuyBonusTownBlocks(Town town, int inputN, CommandSender s
double cost = town.getBonusBlockCostN(n);
// Test if the town can pay and throw economy exception if not.
if (!town.getAccount().canPayFromHoldings(cost))
- throw new TownyException(Translatable.of("msg_no_funds_to_buy", n, Translatable.of("bonus_townblocks"), TownyEconomyHandler.getFormattedBalance(cost)));
+ throw new TownyException(Translatable.of("msg_no_funds_to_buy", n, Translatable.of("bonus_townblocks"), prettyMoney(cost)));
Confirmation.runOnAccept(() -> {
town.addPurchasedBlocks(n);
- TownyMessaging.sendMsg(sender, Translatable.of("msg_buy", n, Translatable.of("bonus_townblocks"), TownyEconomyHandler.getFormattedBalance(cost)));
+ TownyMessaging.sendMsg(sender, Translatable.of("msg_buy", n, Translatable.of("bonus_townblocks"), prettyMoney(cost)));
town.save();
})
- .setCost(new ConfirmationTransaction(() -> cost, town.getAccount(), String.format("Town Buy Bonus (%d)", n),
- Translatable.of("msg_no_funds_to_buy", n, Translatable.of("bonus_townblocks"), TownyEconomyHandler.getFormattedBalance(cost))))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(cost)))
+ .setCost(new ConfirmationTransaction(() -> cost, town, String.format("Town Buy Bonus (%d)", n),
+ Translatable.of("msg_no_funds_to_buy", n, Translatable.of("bonus_townblocks"), prettyMoney(cost))))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
.sendTo(sender);
}
@@ -2587,8 +2587,9 @@ public static void newTown(Player player, String name, Resident resident, boolea
}
// Test if the resident can afford the town.
- if (!resident.getAccount().canPayFromHoldings(TownySettings.getNewTownPrice()))
- throw new TownyException(Translatable.of("msg_no_funds_new_town2", (resident.getName().equals(player.getName()) ? Translatable.of("msg_you") : resident.getName()), TownySettings.getNewTownPrice()));
+ double cost = TownySettings.getNewTownPrice();
+ if (!resident.getAccount().canPayFromHoldings(cost))
+ throw new TownyException(Translatable.of("msg_no_funds_new_town2", (resident.getName().equals(player.getName()) ? Translatable.of("msg_you") : resident.getName()), cost));
// Send a confirmation before taking their money and throwing the PreNewTownEvent.
final String finalName = name;
@@ -2603,9 +2604,9 @@ public static void newTown(Player player, String name, Resident resident, boolea
}
})
.setCancellableEvent(new PreNewTownEvent(player, name, spawnLocation))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(TownySettings.getNewTownPrice())))
- .setCost(new ConfirmationTransaction(TownySettings::getNewTownPrice, resident.getAccount(), "New Town Cost",
- Translatable.of("msg_no_funds_new_town2", (resident.getName().equals(player.getName()) ? Translatable.of("msg_you") : resident.getName()), TownySettings.getNewTownPrice())))
+ .setTitle(Translatable.of("msg_confirm_purchase", prettyMoney(cost)))
+ .setCost(new ConfirmationTransaction(() -> cost, resident, "New Town Cost",
+ Translatable.of("msg_no_funds_new_town2", (resident.getName().equals(player.getName()) ? Translatable.of("msg_you") : resident.getName()), prettyMoney(cost))))
.sendTo(player);
}
@@ -2751,7 +2752,7 @@ public static void townRename(CommandSender sender, Town town, String newName) {
double renameCost = TownySettings.getTownRenameCost();
if (TownyEconomyHandler.isActive() && renameCost > 0 && !town.getAccount().withdraw(renameCost, String.format("Town renamed to: %s", newName))) {
- TownyMessaging.sendErrorMsg(sender, Translatable.of("msg_err_no_money", TownyEconomyHandler.getFormattedBalance(renameCost)));
+ TownyMessaging.sendErrorMsg(sender, Translatable.of("msg_err_no_money", prettyMoney(renameCost)));
return;
}
@@ -3637,7 +3638,7 @@ else if (finalSelection.size() == 1)
if (!town.getAccount().canPayFromHoldings(blockCost)) {
double missingAmount = blockCost - town.getAccount().getHoldingBalance();
- throw new TownyException(Translatable.of("msg_no_funds_claim2", finalSelection.size(), TownyEconomyHandler.getFormattedBalance(blockCost), TownyEconomyHandler.getFormattedBalance(missingAmount), new DecimalFormat("#").format(missingAmount)));
+ throw new TownyException(Translatable.of("msg_no_funds_claim2", finalSelection.size(), prettyMoney(blockCost), prettyMoney(missingAmount), new DecimalFormat("#").format(missingAmount)));
}
town.getAccount().withdraw(blockCost, String.format("Town Claim (%d) by %s", finalSelection.size(), player.getName()));
@@ -3721,19 +3722,19 @@ public static void parseTownUnclaimCommand(Player player, String[] split) throws
if (TownyEconomyHandler.isActive() && TownySettings.getClaimRefundPrice() < 0) {
double cost = Math.abs(TownySettings.getClaimRefundPrice() * selection.size());
if (!town.getAccount().canPayFromHoldings(cost)) {
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_your_town_cannot_afford_unclaim", TownyEconomyHandler.getFormattedBalance(cost)));
+ TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_your_town_cannot_afford_unclaim", prettyMoney(cost)));
return;
}
List finalSelection = selection;
Confirmation.runOnAccept(()-> {
if (!town.getAccount().canPayFromHoldings(cost)) {
- TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_your_town_cannot_afford_unclaim", TownyEconomyHandler.getFormattedBalance(cost)));
+ TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_your_town_cannot_afford_unclaim", prettyMoney(cost)));
return;
}
// Set the area to unclaim
plugin.getScheduler().runAsync(new TownClaim(plugin, player, town, finalSelection, false, false, false));
})
- .setTitle(Translatable.of("confirmation_unclaiming_costs", TownyEconomyHandler.getFormattedBalance(cost)))
+ .setTitle(Translatable.of("confirmation_unclaiming_costs", prettyMoney(cost)))
.sendTo(player);
return;
}
@@ -3751,7 +3752,7 @@ private static void parseTownUnclaimAllCommand(Player player, Town town, Residen
if (TownyEconomyHandler.isActive() && TownySettings.getClaimRefundPrice() < 0) {
int numTownBlocks = town.getTownBlocks().size() - (town.hasHomeBlock() ? 1 : 0);
- String formattedCost = TownyEconomyHandler.getFormattedBalance(Math.abs(TownySettings.getClaimRefundPrice() * numTownBlocks));
+ String formattedCost = prettyMoney(Math.abs(TownySettings.getClaimRefundPrice() * numTownBlocks));
// Unclaiming will cost the player money because of a negative refund price. Have them confirm the cost.
Confirmation
.runOnAcceptAsync(new TownClaim(plugin, player, town, null, false, false, false))
@@ -3825,11 +3826,11 @@ private void parseTownTakeoverClaimCommand(Player player) throws TownyException
throw new TownyException(Translatable.of("msg_err_another_plugin_cancelled_takeover"));
double cost = TownySettings.getTakeoverClaimPrice();
- String costSlug = !TownyEconomyHandler.isActive() || cost <= 0 ? Translatable.of("msg_spawn_cost_free").forLocale(player) : TownyEconomyHandler.getFormattedBalance(cost);
+ String costSlug = !TownyEconomyHandler.isActive() || cost <= 0 ? Translatable.of("msg_spawn_cost_free").forLocale(player) : prettyMoney(cost);
String townName = wc.getTownOrNull().getName();
Confirmation.runOnAccept(() -> Bukkit.getScheduler().runTask(plugin, new TownClaim(plugin, player, town, Arrays.asList(wc), false, true, false)))
.setTitle(Translatable.of("confirmation_you_are_about_to_take_over_a_claim", townName, costSlug))
- .setCost(new ConfirmationTransaction(() -> cost, town.getAccount(), "Takeover Claim (" + wc.toString() + ") from " + townName + "."))
+ .setCost(new ConfirmationTransaction(() -> cost, town, "Takeover Claim (" + wc.toString() + ") from " + townName + "."))
.sendTo(player);
}
@@ -3972,10 +3973,6 @@ private static double[] getMergeCosts(Town remainingTown, Town succumbingTown, b
return mergeCost;
}
- private static String prettyMoney(double cost) {
- return TownyEconomyHandler.getFormattedBalance(cost);
- }
-
private static void sendTownMergeRequest(CommandSender sender, Town remainingTown, Town succumbingTown, double cost) {
TownyMessaging.sendMsg(sender, Translatable.of("msg_town_merge_request_sent", succumbingTown.getName()));
TownyMessaging.sendMsg(succumbingTown.getMayor(), Translatable.of("msg_town_merge_request_received", remainingTown.getName(), sender.getName(), remainingTown.getName()));
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java
index 5172e148ff..f2614b2077 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java
@@ -172,6 +172,7 @@ public class TownyAdminCommand extends BaseCommand implements CommandExecutor {
"rename",
"delete",
"toggle",
+ "sanctiontown",
"set",
"meta",
"deposit",
@@ -508,6 +509,9 @@ else if (args.length > 3 && TownyCommandAddonAPI.hasCommand(CommandType.TOWNYADM
if (args.length == 2) {
return filterByStartOrGetTownyStartingWith(Collections.singletonList("new"), args[1], "+n");
} else if (args.length > 2 && !args[1].equalsIgnoreCase("new")) {
+ Nation nation = TownyUniverse.getInstance().getNation(args[1]);
+ if (nation == null)
+ return Collections.emptyList();
switch (args[2].toLowerCase(Locale.ROOT)) {
case "add":
if (args.length == 4)
@@ -518,14 +522,8 @@ else if (args.length > 3 && TownyCommandAddonAPI.hasCommand(CommandType.TOWNYADM
else if (args.length == 5)
return NameUtil.filterByStart(BaseCommand.setOnOffCompletes, args[4]);
case "set": {
- Nation nation = TownyUniverse.getInstance().getNation(args[1]);
- if (nation != null) {
- if (args.length == 4) {
- return NameUtil.filterByStart(adminNationSetTabCompletes, args[3]);
- }
- }
- else {
- return Collections.emptyList();
+ if (args.length == 4) {
+ return NameUtil.filterByStart(adminNationSetTabCompletes, args[3]);
}
}
case "meta":
@@ -546,6 +544,16 @@ else if (args.length == 5)
return getTownyStartingWith(args[4], "r");
else if (args.length == 6)
return NameUtil.filterByStart(TownyPerms.getNationRanks(), args[5]);
+ case "sanctiontown":
+ if (args.length == 4)
+ return NameUtil.filterByStart(Arrays.asList("add","remove","list"), args[3]);
+ if (args.length == 5 && args[3].equalsIgnoreCase("add") || args[4].equalsIgnoreCase("remove"))
+ return NameUtil.filterByStart(TownyUniverse.getInstance().getTowns()
+ .stream()
+ .filter(t -> !nation.hasTown(t))
+ .map(Town::getName)
+ .collect(Collectors.toList()), args[4]);
+ break;
case "enemy":
case "ally":
if (args.length == 4)
@@ -1633,6 +1641,10 @@ public void parseAdminNationCommand(CommandSender sender, String[] split) throws
checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_NATION_KICK.getNode());
NationCommand.nationKick(sender, nation, TownyAPI.getInstance().getTowns(StringMgmt.remArgs(split, 2)));
break;
+ case "sanctiontown":
+ checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_NATION_SANCTIONTOWN.getNode());
+ NationCommand.nationSanctionTown(sender, nation, StringMgmt.remArgs(split, 2));
+ break;
case "delete":
checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_NATION_DELETE.getNode());
Confirmation.runOnAccept(() -> {
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyCommand.java
index fe6bd9fff1..75d7f6932f 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyCommand.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyCommand.java
@@ -412,10 +412,10 @@ public List getTownyPrices(Town town, Translator translator) {
nation = town.getNationOrNull();
output.add(ChatTools.formatTitle(translator.of("towny_prices_title")));
- output.add(translator.of("towny_prices_town_nation", getMoney(TownySettings.getNewTownPrice()), getMoney(TownySettings.getNewNationPrice())));
- output.add(translator.of("towny_prices_reclaim", getMoney(TownySettings.getEcoPriceReclaimTown())));
+ output.add(translator.of("towny_prices_town_nation", prettyMoney(TownySettings.getNewTownPrice()), prettyMoney(TownySettings.getNewNationPrice())));
+ output.add(translator.of("towny_prices_reclaim", prettyMoney(TownySettings.getEcoPriceReclaimTown())));
if (town != null) {
- output.add(translator.of("towny_prices_upkeep", getMoney(TownySettings.getTownUpkeepCost(town)), getMoney(TownySettings.getNationUpkeepCost(nation))));
+ output.add(translator.of("towny_prices_upkeep", prettyMoney(TownySettings.getTownUpkeepCost(town)), prettyMoney(TownySettings.getNationUpkeepCost(nation))));
output.add(translator.of("towny_prices_upkeep_based_on", (TownySettings.isUpkeepByPlot() ? translator.of("towny_prices_upkeep_num_plots") : translator.of("towny_prices_upkeep_town_level"))));
String upkeepformula;
if (TownySettings.isNationUpkeepPerPlot())
@@ -426,36 +426,36 @@ else if (TownySettings.isNationUpkeepPerTown())
upkeepformula = translator.of("towny_prices_upkeep_nation_level");
output.add(translator.of("towny_prices_nation_upkeep_based_on", upkeepformula));
if (town.isOverClaimed() && TownySettings.getUpkeepPenalty() > 0)
- output.add(translator.of("towny_prices_overclaimed_upkeep", getMoney(TownySettings.getTownPenaltyUpkeepCost(town))));
+ output.add(translator.of("towny_prices_overclaimed_upkeep", prettyMoney(TownySettings.getTownPenaltyUpkeepCost(town))));
if (TownySettings.getUpkeepPenalty() > 0 )
output.add(translator.of("towny_prices_overclaimed_based_on", (TownySettings.isUpkeepPenaltyByPlot() ? translator.of("towny_prices_overclaimed_num_plots") : translator.of("towny_prices_overclaimed_flat_cost")), TownySettings.getUpkeepPenalty()));
- output.add(translator.of("towny_prices_town_merge", getMoney(TownySettings.getBaseCostForTownMerge()), getMoney(town.getTownBlockCost()/2)));
- output.add(translator.of("towny_prices_claiming_townblock", getMoney(town.getTownBlockCost()) +
+ output.add(translator.of("towny_prices_town_merge", prettyMoney(TownySettings.getBaseCostForTownMerge()), prettyMoney(town.getTownBlockCost()/2)));
+ output.add(translator.of("towny_prices_claiming_townblock", prettyMoney(town.getTownBlockCost()) +
(Double.valueOf(TownySettings.getClaimPriceIncreaseValue()).equals(1.0) ? "" : translator.of("towny_prices_claiming_townblock_increase", new DecimalFormat("##.##%").format(TownySettings.getClaimPriceIncreaseValue()-1)))));
- output.add(translator.of("towny_prices_claiming_outposts", getMoney(TownySettings.getOutpostCost())));
+ output.add(translator.of("towny_prices_claiming_outposts", prettyMoney(TownySettings.getOutpostCost())));
}
if (town == null)
- output.add(translator.of("towny_prices_upkeep", getMoney(TownySettings.getTownUpkeep()), getMoney(TownySettings.getNationUpkeep())));
+ output.add(translator.of("towny_prices_upkeep", prettyMoney(TownySettings.getTownUpkeep()), prettyMoney(TownySettings.getNationUpkeep())));
if (town != null) {
output.add(translator.of("towny_prices_townname", town.getFormattedName()));
- output.add(translator.of("towny_prices_price_plot", getMoney(town.getPlotPrice()),getMoney(TownySettings.getOutpostCost())));
- output.add(translator.of("towny_prices_price_shop", getMoney(town.getCommercialPlotPrice()), getMoney(town.getEmbassyPlotPrice())));
+ output.add(translator.of("towny_prices_price_plot", prettyMoney(town.getPlotPrice()),prettyMoney(TownySettings.getOutpostCost())));
+ output.add(translator.of("towny_prices_price_shop", prettyMoney(town.getCommercialPlotPrice()), prettyMoney(town.getEmbassyPlotPrice())));
- output.add(translator.of("towny_prices_taxes_plot", (town.isTaxPercentage()? town.getTaxes() + "%" : getMoney(town.getTaxes())), getMoney(town.getPlotTax())));
- output.add(translator.of("towny_prices_taxes_shop", getMoney(town.getCommercialPlotTax()), getMoney(town.getEmbassyPlotTax())));
- output.add(translator.of("towny_prices_town_neutral_tax", getMoney(TownySettings.getTownNeutralityCost(town))));
+ output.add(translator.of("towny_prices_taxes_plot", (town.isTaxPercentage()? town.getTaxes() + "%" : prettyMoney(town.getTaxes())), prettyMoney(town.getPlotTax())));
+ output.add(translator.of("towny_prices_taxes_shop", prettyMoney(town.getCommercialPlotTax()), prettyMoney(town.getEmbassyPlotTax())));
+ output.add(translator.of("towny_prices_town_neutral_tax", prettyMoney(TownySettings.getTownNeutralityCost(town))));
output.add(translator.of("towny_prices_plots"));
List townBlockTypes = new ArrayList<>(TownBlockTypeHandler.getTypes().values());
for (int i = 0; i < townBlockTypes.size(); i++) {
if (i == townBlockTypes.size() - 1)
- output.add(translator.of("towny_prices_type_single", townBlockTypes.get(i).getFormattedName(), getMoney(townBlockTypes.get(i).getCost())));
+ output.add(translator.of("towny_prices_type_single", townBlockTypes.get(i).getFormattedName(), prettyMoney(townBlockTypes.get(i).getCost())));
else {
output.add(translator.of("towny_prices_type_double",
- townBlockTypes.get(i).getFormattedName(), getMoney(townBlockTypes.get(i).getCost()),
- townBlockTypes.get(i+1).getFormattedName(), getMoney(townBlockTypes.get(i+1).getCost())
+ townBlockTypes.get(i).getFormattedName(), prettyMoney(townBlockTypes.get(i).getCost()),
+ townBlockTypes.get(i+1).getFormattedName(), prettyMoney(townBlockTypes.get(i+1).getCost())
));
i++;
@@ -464,16 +464,12 @@ else if (TownySettings.isNationUpkeepPerTown())
if (nation != null) {
output.add(translator.of("towny_prices_nationname", nation.getFormattedName()));
- output.add(translator.of("towny_prices_nation_tax", (nation.isTaxPercentage() ? nation.getTaxes() + "%" : getMoney(nation.getTaxes())), getMoney(TownySettings.getNationNeutralityCost(nation))));
+ output.add(translator.of("towny_prices_nation_tax", (nation.isTaxPercentage() ? nation.getTaxes() + "%" : prettyMoney(nation.getTaxes())), prettyMoney(TownySettings.getNationNeutralityCost(nation))));
}
}
return output;
}
-
- private String getMoney(double cost) {
- return TownyEconomyHandler.getFormattedBalance(cost);
- }
-
+
public List getTopBankBalance(final List governments) {
final int maxListing = TownySettings.getTownyTopSize();
final List output = new ArrayList<>();
@@ -488,7 +484,7 @@ public List getTopBankBalance(final List governments) {
if (maxListing != -1 && index > maxListing) {
break;
}
- output.add(String.format(Colors.LightGray + "%-20s " + Colors.Gold + "|" + Colors.Blue + " %s", gov.getFormattedName(), getMoney(gov.getAccount().getCachedBalance())));
+ output.add(String.format(Colors.LightGray + "%-20s " + Colors.Gold + "|" + Colors.Blue + " %s", gov.getFormattedName(), prettyMoney(gov.getAccount().getCachedBalance())));
}
return output;
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationHandler.java b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationHandler.java
index f93c5b4c85..25ddd466c1 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationHandler.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationHandler.java
@@ -141,9 +141,9 @@ public static void acceptConfirmation(CommandSender sender) {
// Determine the cost, done in this phase in case the cost could be manipulated before confirming.
transaction.supplyCost();
double cost = transaction.getCost();
+ Account payee = transaction.getPayee();
// Can they pay the cost?
- if (cost > 0) {
- Account payee = transaction.getPayee();
+ if (cost > 0 && payee != null) {
if (!payee.canPayFromHoldings(cost)) {
TownyMessaging.sendErrorMsg(sender, transaction.getInsufficientFundsMessage());
TownyMessaging.sendErrorMsg(sender, Translatable.of("msg_err_you_need_x_to_pay", cost));
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationTransaction.java b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationTransaction.java
index 490f9b8f43..da0b6927ee 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationTransaction.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationTransaction.java
@@ -2,7 +2,10 @@
import java.util.function.Supplier;
+import org.jetbrains.annotations.Nullable;
+
import com.palmergames.bukkit.towny.TownyEconomyHandler;
+import com.palmergames.bukkit.towny.object.EconomyHandler;
import com.palmergames.bukkit.towny.object.Translatable;
import com.palmergames.bukkit.towny.object.economy.Account;
@@ -17,15 +20,15 @@ public class ConfirmationTransaction {
* A transaction which must succeed for a Confirmation to complete.
*
* @param costSupplier cost of the transaction.
- * @param payee Account which will have to pay.
+ * @param townyObject EconomyHandler which will have to pay, this will be a Resident, Town or Nation.
* @param loggedMessage The message logged in the money.csv file.
* @param insufficientFundsMessage Transatable which will display the cannot pay message.
*/
- public ConfirmationTransaction(Supplier costSupplier, Account payee, String loggedMessage, Translatable insufficientFundsMessage) {
+ public ConfirmationTransaction(Supplier costSupplier, EconomyHandler townyObject, String loggedMessage, Translatable insufficientFundsMessage) {
this.costSupplier = costSupplier;
- this.payee = payee;
this.loggedMessage = loggedMessage;
this.insufficientFundsMessage = insufficientFundsMessage;
+ this.payee = !TownyEconomyHandler.isActive() ? null : townyObject.getAccount();
}
/**
@@ -33,14 +36,14 @@ public ConfirmationTransaction(Supplier costSupplier, Account payee, Str
* Uses the default no money error message.
*
* @param costSupplier cost of the transaction.
- * @param payee Account which will have to pay.
+ * @param townyObject EconomyHandler which will have to pay, this will be a Resident, Town or Nation.
* @param loggedMessage The message logged in the money.csv file.
*/
- public ConfirmationTransaction(Supplier costSupplier, Account payee, String loggedMessage) {
+ public ConfirmationTransaction(Supplier costSupplier, EconomyHandler townyObject, String loggedMessage) {
this.costSupplier = costSupplier;
- this.payee = payee;
this.loggedMessage = loggedMessage;
this.insufficientFundsMessage = null;
+ this.payee = !TownyEconomyHandler.isActive() ? null : townyObject.getAccount();
}
public void supplyCost() {
@@ -51,6 +54,7 @@ public double getCost() {
return cost;
}
+ @Nullable
public Account getPayee() {
return payee;
}
@@ -62,4 +66,38 @@ public String getLoggedMessage() {
public Translatable getInsufficientFundsMessage() {
return insufficientFundsMessage != null ? insufficientFundsMessage : Translatable.of("msg_err_no_money", TownyEconomyHandler.getFormattedBalance(getCost()));
}
+
+ /**
+ * A transaction which must succeed for a Confirmation to complete.
+ *
+ * @deprecated since 0.100.0.10 use {@link #ConfirmationTransaction(Supplier, EconomyHandler, String, Translatable)} instead.
+ * @param costSupplier cost of the transaction.
+ * @param payee Account which will have to pay.
+ * @param loggedMessage The message logged in the money.csv file.
+ * @param insufficientFundsMessage Transatable which will display the cannot pay message.
+ */
+ @Deprecated
+ public ConfirmationTransaction(Supplier costSupplier, Account payee, String loggedMessage, Translatable insufficientFundsMessage) {
+ this.costSupplier = costSupplier;
+ this.payee = payee;
+ this.loggedMessage = loggedMessage;
+ this.insufficientFundsMessage = insufficientFundsMessage;
+ }
+
+ /**
+ * A transaction which must succeed for a Confirmation to complete.
+ * Uses the default no money error message.
+ *
+ * @deprecated since 0.100.0.10 use {@link #ConfirmationTransaction(Supplier, EconomyHandler, String)} instead.
+ * @param costSupplier cost of the transaction.
+ * @param payee Account which will have to pay.
+ * @param loggedMessage The message logged in the money.csv file.
+ */
+ @Deprecated
+ public ConfirmationTransaction(Supplier costSupplier, Account payee, String loggedMessage) {
+ this.costSupplier = costSupplier;
+ this.payee = payee;
+ this.loggedMessage = loggedMessage;
+ this.insufficientFundsMessage = null;
+ }
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/db/SQLSchema.java b/Towny/src/main/java/com/palmergames/bukkit/towny/db/SQLSchema.java
index db381c9024..7f59bfadd2 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/db/SQLSchema.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/db/SQLSchema.java
@@ -269,6 +269,7 @@ private static List getNationColumns(){
columns.add("`isOpen` bool NOT NULL DEFAULT '1'");
columns.add("`metadata` text DEFAULT NULL");
columns.add("`conqueredTax` float NOT NULL");
+ columns.add("`sanctionedTowns` mediumtext DEFAULT NULL");
return columns;
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyFlatFileSource.java b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyFlatFileSource.java
index 7c95e759c7..db38f3f0a5 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyFlatFileSource.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyFlatFileSource.java
@@ -1218,6 +1218,11 @@ public boolean loadNation(Nation nation) {
if (line != null && !line.isEmpty())
nation.setConqueredTax(Double.parseDouble(line));
+ line = keys.get("sanctionedTowns");
+ if (line != null) {
+ nation.loadSanctionedTowns(line.split("#"));
+ }
+
} catch (Exception e) {
plugin.getLogger().log(Level.WARNING, Translation.of("flatfile_err_reading_nation_file_at_line", nation.getName(), line, nation.getName()), e);
return false;
@@ -2136,6 +2141,9 @@ public boolean saveNation(Nation nation) {
list.add("metadata=" + serializeMetadata(nation));
list.add("conqueredTax=" + nation.getConqueredTax());
+
+ // SanctionedTowns
+ list.add("sanctionedTowns=" + StringMgmt.join(nation.getSanctionedTownsForSaving(), "#"));
/*
* Make sure we only save in async
*/
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownySQLSource.java b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownySQLSource.java
index 5c54d58866..9c3e0cd008 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownySQLSource.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownySQLSource.java
@@ -1387,6 +1387,11 @@ private boolean loadNation(ResultSet rs) {
} catch (SQLException ignored) {
}
+ line = rs.getString("sanctionedTowns");
+ if (line != null) {
+ nation.loadSanctionedTowns(line.split("#"));
+ }
+
return true;
} catch (SQLException e) {
TownyMessaging.sendErrorMsg("SQL: Load Nation " + name + " SQL Error - " + e.getMessage());
@@ -2349,7 +2354,7 @@ public synchronized boolean saveNation(Nation nation) {
nat_hm.put("metadata", "");
nat_hm.put("conqueredTax", nation.getConqueredTax());
-
+ nat_hm.put("sanctionedTowns", StringMgmt.join(nation.getSanctionedTownsForSaving(), "#"));
updateDB("NATIONS", nat_hm, Collections.singletonList("name"));
} catch (Exception e) {
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationSanctionTownAddEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationSanctionTownAddEvent.java
new file mode 100644
index 0000000000..34f31113b0
--- /dev/null
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationSanctionTownAddEvent.java
@@ -0,0 +1,38 @@
+package com.palmergames.bukkit.towny.event.nation;
+
+import com.palmergames.bukkit.towny.event.CancellableTownyEvent;
+import com.palmergames.bukkit.towny.object.Nation;
+import com.palmergames.bukkit.towny.object.Town;
+
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class NationSanctionTownAddEvent extends CancellableTownyEvent {
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+
+ private final Nation nation;
+ private final Town town;
+
+ public NationSanctionTownAddEvent(Nation nation, Town town) {
+ this.nation = nation;
+ this.town = town;
+ }
+
+ public Nation getNation() {
+ return nation;
+ }
+
+ public Town getTown() {
+ return town;
+ }
+
+ public static HandlerList getHandlerList() {
+ return HANDLER_LIST;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLER_LIST;
+ }
+}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationSanctionTownRemoveEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationSanctionTownRemoveEvent.java
new file mode 100644
index 0000000000..75517e988d
--- /dev/null
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationSanctionTownRemoveEvent.java
@@ -0,0 +1,38 @@
+package com.palmergames.bukkit.towny.event.nation;
+
+import com.palmergames.bukkit.towny.event.CancellableTownyEvent;
+import com.palmergames.bukkit.towny.object.Nation;
+import com.palmergames.bukkit.towny.object.Town;
+
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class NationSanctionTownRemoveEvent extends CancellableTownyEvent {
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+
+ private final Nation nation;
+ private final Town town;
+
+ public NationSanctionTownRemoveEvent(Nation nation, Town town) {
+ this.nation = nation;
+ this.town = town;
+ }
+
+ public Nation getNation() {
+ return nation;
+ }
+
+ public Town getTown() {
+ return town;
+ }
+
+ public static HandlerList getHandlerList() {
+ return HANDLER_LIST;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLER_LIST;
+ }
+}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Nation.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Nation.java
index c57c5718e7..cdaf705950 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Nation.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Nation.java
@@ -34,6 +34,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.UUID;
import java.util.stream.Collectors;
public class Nation extends Government {
@@ -41,6 +42,7 @@ public class Nation extends Government {
private static final String ECONOMY_ACCOUNT_PREFIX = TownySettings.getNationAccountPrefix();
private final List towns = new ArrayList<>();
+ private final List sanctionedTowns = new ArrayList<>();
private List allies = new ArrayList<>();
private List enemies = new ArrayList<>();
private Town capital;
@@ -698,4 +700,38 @@ public int getLevel(int populationSize) {
public void playerBroadCastMessageToNation(Player player, String message) {
TownyMessaging.sendPrefixedNationMessage(this, Translatable.of("town_say_format", player.getName(), TownyComponents.stripClickTags(message)));
}
+
+
+ public List getSanctionedTowns() {
+ return sanctionedTowns;
+ }
+
+ public boolean hasSanctionedTown(Town town) {
+ return sanctionedTowns.contains(town);
+ }
+
+ public void addSanctionedTown(Town town) {
+ if (!sanctionedTowns.contains(town))
+ sanctionedTowns.add(town);
+ }
+
+ public void removeSanctionedTown(Town town) {
+ sanctionedTowns.remove(town);
+ }
+
+ public List getSanctionedTownsForSaving() {
+ return sanctionedTowns.stream().map(t -> t.getUUID().toString()).collect(Collectors.toList());
+ }
+
+ public void loadSanctionedTowns(String[] tokens) {
+ for (String stringUUID : tokens) {
+ try {
+ Town town = TownyAPI.getInstance().getTown(UUID.fromString(stringUUID));
+ if (town != null)
+ sanctionedTowns.add(town);
+ } catch (IllegalArgumentException ignored) {
+ continue;
+ }
+ }
+ }
}
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 f81f85110e..6c5a59dafe 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
@@ -90,6 +90,7 @@ public enum PermissionNodes {
TOWNY_COMMAND_NATION_BANKHISTORY("towny.command.nation.bankhistory"),
TOWNY_COMMAND_NATION_BALTOP("towny.command.nation.baltop"),
TOWNY_COMMAND_NATION_KICK("towny.command.nation.kick"),
+ TOWNY_COMMAND_NATION_SANCTIONTOWN("towny.command.nation.sanctiontown"),
/*
* Town command permissions
@@ -338,6 +339,7 @@ public enum PermissionNodes {
TOWNY_COMMAND_TOWNYADMIN_NATION_RECHECK("towny.command.townyadmin.nation.recheck"),
TOWNY_COMMAND_TOWNYADMIN_NATION_RENAME("towny.command.townyadmin.nation.rename"),
TOWNY_COMMAND_TOWNYADMIN_NATION_MERGE("towny.command.townyadmin.nation.merge"),
+ TOWNY_COMMAND_TOWNYADMIN_NATION_SANCTIONTOWN("towny.command.townyadmin.nation.sanctiontown"),
TOWNY_COMMAND_TOWNYADMIN_NATION_SET("towny.command.townyadmin.nation.set"),
TOWNY_COMMAND_TOWNYADMIN_NATION_SETFOUNDINGDATE("towny.command.townyadmin.nation.set.foundingdate"),
TOWNY_COMMAND_TOWNYADMIN_NATION_TOGGLE("towny.command.townyadmin.nation.toggle"),
diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownRuinUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownRuinUtil.java
index e2fb73ebf1..506d7a9a19 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownRuinUtil.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownRuinUtil.java
@@ -153,14 +153,11 @@ public static void processRuinedTownReclaimRequest(Player player) {
if (TownySettings.getTownRuinsMinDurationHours() - getTimeSinceRuining(town) > 0)
throw new TownyException(Translatable.of("msg_err_cannot_reclaim_town_yet", TownySettings.getTownRuinsMinDurationHours() - getTimeSinceRuining(town)));
- if (TownyEconomyHandler.isActive() && townReclaimCost > 0) {
- Confirmation.runOnAccept(() -> reclaimTown(resident, town))
- .setCost(new ConfirmationTransaction(() -> townReclaimCost, resident.getAccount(), "Cost of town reclaim.", Translatable.of("msg_insuf_funds")))
- .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(townReclaimCost)))
- .sendTo(player);
- } else {
- reclaimTown(resident, town);
- }
+ //Ask them to confirm they want to reclaim the town.
+ Confirmation.runOnAccept(() -> reclaimTown(resident, town))
+ .setCost(new ConfirmationTransaction(() -> townReclaimCost, resident, "Cost of town reclaim.", Translatable.of("msg_insuf_funds")))
+ .setTitle(Translatable.of("msg_confirm_purchase", TownyEconomyHandler.getFormattedBalance(townReclaimCost)))
+ .sendTo(player);
} catch (TownyException e) {
TownyMessaging.sendErrorMsg(player, e.getMessage(player));
}
diff --git a/Towny/src/main/java/com/palmergames/bukkit/util/NameValidation.java b/Towny/src/main/java/com/palmergames/bukkit/util/NameValidation.java
index 6bcc8d0395..8e90a68361 100644
--- a/Towny/src/main/java/com/palmergames/bukkit/util/NameValidation.java
+++ b/Towny/src/main/java/com/palmergames/bukkit/util/NameValidation.java
@@ -29,7 +29,7 @@ public class NameValidation {
"outlawlist","deposit","outlaw","outpost","ranklist","rank","reclaim","reslist","say","set","toggle","join",
"invite","buy","mayor","bankhistory","enemy","ally","townlist","allylist","enemylist","king","merge","jail",
"plotgrouplist","trust","purge","leader","baltop","all","help", "spawn", "takeoverclaim", "ban", "unjail",
- "trusttown","forsale","fs","notforsale","nfs","buytown"));
+ "trusttown","forsale","fs","notforsale","nfs","buytown","sanctiontown"));
}
/**
diff --git a/Towny/src/main/resources/ChangeLog.txt b/Towny/src/main/resources/ChangeLog.txt
index fc35a08749..b708dc797c 100644
--- a/Towny/src/main/resources/ChangeLog.txt
+++ b/Towny/src/main/resources/ChangeLog.txt
@@ -9285,4 +9285,34 @@ v0.92.0.11:
- This setting is only used when nation_proximity_to_capital_city is above 0.
- Leave this setting at 0.0 in order to allow nations to chain towns together to go as wide as they like.
0.100.0.11:
- - Fix config migrator file formatting, courtesy of Warrior with PR #7128.
\ No newline at end of file
+ - Fix config migrator file formatting, courtesy of Warrior with PR #7128.
+0.100.0.12:
+ - Bump adventure-platform to 4.3.2 for 1.20.4 compatibility.
+0.100.0.13:
+ - Bump org.apache.maven.plugins:maven-surefire-plugin from 3.1.2 to 3.2.3.
+ - Bump com.github.seeseemelk:MockBukkit-v1.20 from 3.56.0 to 3.58.0.
+ - Make Towny more resiliant to errors which come from having no Economy present on the server.
+ - Add the ability for nations to sanction towns, preventing them from joining the nation via invite or join.
+ - Using /n sanctiontown ... kings can add/remove and list their sanctioned towns.
+ - Closes #6544.
+ - New Command: /nation sanctiontown
+ - add [townname] - Adds a town to the sanctioned town list.
+ - remove [townname] - Removes a town from the sanctioned town list.
+ - list - Lists your nation's sanctioned towns.
+ - list [nationname] - Lists the sanctioned towns of other nations.
+ - New Command: /ta nation [nationname] sanctiontown
+ - add [townname] - Adds a town to the sanctioned town list.
+ - remove [townname] - Removes a town from the sanctioned town list.
+ - list - Lists the nation's sanctioned towns.
+ - New Permission Node: towny.command.nation.sanctiontown
+ - Allows adding/removing sanctioned towns, a child node of towny.command.nation.*.
+ - No townyperms.yml change is required.
+ - New Permission Node: towny.command.townyadmin.nation.sanctiontown
+ - Allows admins to add/remove sanctioned towns from a given nation, a child node of towny.command.townyadmin.nation.*.
+ - No townyperms.yml change is required.
+ - API: Added NationSanctionTownAddEvent
+ - A cancellable event which is fired before a town becomes sanctioned.
+ - API: Added NationSanctionTownRemoveEvent
+ - A cancellable event which is fired before a town becomes unsanctioned.
+0.100.0.14:
+ - Fix potential IllegalArgumentException by loading sanctioned towns in a safer way.
\ No newline at end of file
diff --git a/Towny/src/main/resources/lang/de-DE.yml b/Towny/src/main/resources/lang/de-DE.yml
index 3ded6ba674..4a6895cb67 100644
--- a/Towny/src/main/resources/lang/de-DE.yml
+++ b/Towny/src/main/resources/lang/de-DE.yml
@@ -103,13 +103,13 @@ town_list_help_5: "Listet Städte nach beanspruchtem Land mit der angegebenen Se
town_list_help_6: "Listet die Städte, mit den meisten Bewohnern online mit der angegebenen Seite auf."
town_set_help_0: "Set your town's board."
town_set_help_1: "Set your town's homeblock to where you're standing."
-town_set_help_2: "Sets the spawn point in your homeblock or an outpost spawnpoint when stood in an outpost."
+town_set_help_2: "Setzt deinen Stadt spawn Punkt, oder Outpost spawn punkt, solange du dich in einem Outpost befindest."
town_set_help_3: "See /town set perm ? for help."
-town_set_help_4: "Set your town's tax amount."
+town_set_help_4: "Setze deine Stadt Steuer fest."
town_set_help_5: "Set the special amounts for per-plot taxes."
-town_set_help_6: "Set the default prices for buying plots."
+town_set_help_6: "Setzte die Grundgebühren für Plots fest."
town_set_help_7: "Set the cost to use /t spawn."
-town_set_help_8: "Set your town's name."
+town_set_help_8: "Lege deinen Stadt-Namen fest."
town_set_help_9: "Set your town's tag."
town_set_help_10: "Set the title or surname of your resident."
town_set_help_11: "Set the max amount of money a percentage tax can take from a resident."
diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml
index ebb3a4c368..c9b567b105 100644
--- a/Towny/src/main/resources/lang/en-US.yml
+++ b/Towny/src/main/resources/lang/en-US.yml
@@ -206,6 +206,11 @@ nation_toggle_help_1: "Toggles public status, allowing non-residents to use /n s
nation_toggle_help_2: "Toggles open status, allowing towns to join without an invite."
nation_toggle_help_3: "Toggles taxpercent, making towns pay a percentage instead of fixed rate."
+nation_sanction_help_1: "Adds a town to the SanctionedTown list."
+nation_sanction_help_2: "Removes a town from the SanctionedTown list."
+nation_sanction_help_3: "Lists your nation's Sanctioned Towns."
+nation_sanction_help_4: "Lists the given nation's Sanctioned Towns."
+
king_help_1: 'Nation Leader Help'
king_help_2: 'Set your alliance.'
king_help_3: 'Set your enemies.'
@@ -2371,4 +2376,24 @@ msg_err_resident_is_npc: "You cannot do this to an NPC resident."
# Message shown when a resident doesn't own any land and the /res plotlist command is used.
msg_err_resident_doesnt_own_any_land: "The resident does not own any land personally."
-msg_unnamed: "Unnamed"
\ No newline at end of file
+msg_unnamed: "Unnamed"
+
+msg_err_nation_cannot_sanction_own_town: "You cannot sanction a town that is a part of your nation."
+
+msg_err_nation_town_isnt_sanctioned: "Your nation is not sanctioning that town."
+
+msg_err_nation_town_already_sanctioned: "Your nation already sanctions that town."
+
+msg_err_nation_town_sanctioned: "Your nation has now sanctioned %s, they will not be allowed to join your nation."
+
+msg_err_nation_town_unsanctioned: "Your nation is no longer sanctioning %s."
+
+msg_err_nation_has_no_sanctioned_towns: "The nation has no sanctioned towns."
+
+title_nation_sanctioned_towns: 'Sanctioned Towns'
+
+msg_err_cannot_add_sanctioned_town: "Your nation cannot add %s, your nation has sanctioned them."
+
+msg_err_cannot_join_nation_sanctioned_town: "Your town cannot join %s, this nation has sanctioned your town."
+
+msg_err_no_nation_cannot_do: "You cannot perform this action because no nation was able to be determined."
\ No newline at end of file
diff --git a/Towny/src/main/resources/plugin.yml b/Towny/src/main/resources/plugin.yml
index 640db5bc98..4986906125 100644
--- a/Towny/src/main/resources/plugin.yml
+++ b/Towny/src/main/resources/plugin.yml
@@ -211,6 +211,7 @@ permissions:
towny.command.nation.othernation: true
towny.command.nation.withdraw: true
towny.command.nation.rank.*: true
+ towny.command.nation.sanctiontown: true
towny.command.nation.say: true
towny.command.nation.set.*: true
towny.command.nation.spawn: true
@@ -746,6 +747,7 @@ permissions:
towny.command.townyadmin.nation.rank: true
towny.command.townyadmin.nation.recheck: true
towny.command.townyadmin.nation.rename: true
+ towny.command.townyadmin.nation.sanctiontown: true
towny.command.townyadmin.nation.set: true
towny.command.townyadmin.nation.set.*: true
towny.command.townyadmin.nation.toggle: true