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 4518e7b6108..43e81d5ea8a 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 @@ -2030,8 +2030,8 @@ public boolean saveResident(Resident resident) { if (resident.hasTown()) { list.add("town=" + resident.getTownOrNull().getName()); - list.add("town-ranks=" + StringMgmt.join(resident.getTownRanks(), ",")); - list.add("nation-ranks=" + StringMgmt.join(resident.getNationRanks(), ",")); + list.add("town-ranks=" + StringMgmt.join(resident.getTownRanksForSaving(), ",")); + list.add("nation-ranks=" + StringMgmt.join(resident.getNationRanksForSaving(), ",")); } // Friends 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 d26e7bed470..906b6d58174 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 @@ -2312,8 +2312,8 @@ public synchronized boolean saveResident(Resident resident) { if (!TownySettings.getDefaultResidentAbout().equals(resident.getAbout())) res_hm.put("about", resident.getAbout()); res_hm.put("town", resident.hasTown() ? resident.getTown().getName() : ""); - res_hm.put("town-ranks", resident.hasTown() ? StringMgmt.join(resident.getTownRanks(), "#") : ""); - res_hm.put("nation-ranks", resident.hasTown() ? StringMgmt.join(resident.getNationRanks(), "#") : ""); + res_hm.put("town-ranks", resident.hasTown() ? StringMgmt.join(resident.getTownRanksForSaving(), "#") : ""); + res_hm.put("nation-ranks", resident.hasTown() ? StringMgmt.join(resident.getNationRanksForSaving(), "#") : ""); res_hm.put("friends", StringMgmt.join(resident.getFriends(), "#")); res_hm.put("protectionStatus", resident.getPermissions().toString().replaceAll(",", "#")); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationLevelDecreaseEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationLevelDecreaseEvent.java new file mode 100644 index 00000000000..67d1ead158b --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationLevelDecreaseEvent.java @@ -0,0 +1,36 @@ +package com.palmergames.bukkit.towny.event.nation; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import com.palmergames.bukkit.towny.object.Nation; + +public class NationLevelDecreaseEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private final Nation nation; + + public NationLevelDecreaseEvent(Nation nation) { + super(!Bukkit.getServer().isPrimaryThread()); + this.nation = nation; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * The nation which has had its Nation_Level decrease. + * + * @return nation which has had its Nation_Level decrease. + */ + public Nation getNation() { + return nation; + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationLevelIncreaseEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationLevelIncreaseEvent.java new file mode 100644 index 00000000000..3b6fee76b1d --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/nation/NationLevelIncreaseEvent.java @@ -0,0 +1,36 @@ +package com.palmergames.bukkit.towny.event.nation; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import com.palmergames.bukkit.towny.object.Nation; + +public class NationLevelIncreaseEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private final Nation nation; + + public NationLevelIncreaseEvent(Nation nation) { + super(!Bukkit.getServer().isPrimaryThread()); + this.nation = nation; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * The nation which has had its Nation_Level increase. + * + * @return nation which has had its Nation_Level increase. + */ + public Nation getNation() { + return nation; + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/town/TownLevelDecreaseEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/town/TownLevelDecreaseEvent.java new file mode 100644 index 00000000000..32ebbc53578 --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/town/TownLevelDecreaseEvent.java @@ -0,0 +1,36 @@ +package com.palmergames.bukkit.towny.event.town; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import com.palmergames.bukkit.towny.object.Town; + +public class TownLevelDecreaseEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private final Town town; + + public TownLevelDecreaseEvent(Town town) { + super(!Bukkit.getServer().isPrimaryThread()); + this.town = town; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * The town which has had its Town_Level decrease. + * + * @return town which has had its Town_Level decrease. + */ + public Town getTown() { + return town; + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/town/TownLevelIncreaseEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/town/TownLevelIncreaseEvent.java new file mode 100644 index 00000000000..99fd6fbfa8c --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/town/TownLevelIncreaseEvent.java @@ -0,0 +1,36 @@ +package com.palmergames.bukkit.towny.event.town; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import com.palmergames.bukkit.towny.object.Town; + +public class TownLevelIncreaseEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private final Town town; + + public TownLevelIncreaseEvent(Town town) { + super(!Bukkit.getServer().isPrimaryThread()); + this.town = town; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * The town which has had its Town_Level increase. + * + * @return town which has had its Town_Level increase. + */ + public Town getTown() { + return town; + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyCustomListener.java b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyCustomListener.java index 5241314bda6..6b123eb6483 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyCustomListener.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyCustomListener.java @@ -21,11 +21,16 @@ import com.palmergames.bukkit.towny.event.TownPreAddResidentEvent; import com.palmergames.bukkit.towny.event.TownRemoveResidentEvent; import com.palmergames.bukkit.towny.event.damage.TownyPlayerDamagePlayerEvent; +import com.palmergames.bukkit.towny.event.nation.NationLevelDecreaseEvent; +import com.palmergames.bukkit.towny.event.nation.NationLevelIncreaseEvent; import com.palmergames.bukkit.towny.event.nation.NationPreTownLeaveEvent; +import com.palmergames.bukkit.towny.event.town.TownLevelDecreaseEvent; +import com.palmergames.bukkit.towny.event.town.TownLevelIncreaseEvent; import com.palmergames.bukkit.towny.event.town.TownPreUnclaimCmdEvent; import com.palmergames.bukkit.towny.event.town.TownPreUnclaimEvent; import com.palmergames.bukkit.towny.exceptions.TownyException; import com.palmergames.bukkit.towny.object.CellSurface; +import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.PlayerCache; import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.SpawnType; @@ -35,11 +40,13 @@ import com.palmergames.bukkit.towny.object.Translatable; import com.palmergames.bukkit.towny.object.Translation; import com.palmergames.bukkit.towny.object.WorldCoord; +import com.palmergames.bukkit.towny.permissions.TownyPerms; import com.palmergames.bukkit.towny.utils.BorderUtil; import com.palmergames.bukkit.towny.utils.ChunkNotificationUtil; import com.palmergames.bukkit.towny.utils.PlayerCacheUtil; import com.palmergames.bukkit.towny.utils.ProximityUtil; import com.palmergames.bukkit.towny.utils.SpawnUtil; +import com.palmergames.bukkit.util.BukkitTools; import com.palmergames.bukkit.util.Colors; import com.palmergames.bukkit.util.DrawSmokeTaskFactory; import com.palmergames.util.TimeMgmt; @@ -258,14 +265,27 @@ public void onTownClaim(TownClaimEvent event) { * Used to warn towns when they've lost a resident, so they know they're at risk * of having claims stolen in the takeoverclaim feature. * + * Used for town_level and nation_level decrease events. + * * @param event TownRemoveResidentEvent. */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onTownLosesResident(TownRemoveResidentEvent event) { + Town town = event.getTown(); + if (town.getLevelNumber() < TownySettings.getTownLevelFromGivenInt(town.getNumResidents() + 1, town)) { + BukkitTools.fireEvent(new TownLevelDecreaseEvent(town)); + } + if (town.hasNation()) { + Nation nation = town.getNationOrNull(); + if (nation.getLevelNumber() < TownySettings.getNationLevelFromGivenInt(nation.getNumResidents() + 1)) { + BukkitTools.fireEvent(new NationLevelDecreaseEvent(nation)); + } + } + if (!TownySettings.isOverClaimingAllowingStolenLand()) return; - if (event.getTown().isOverClaimed()) - TownyMessaging.sendPrefixedTownMessage(event.getTown(), Translatable.literal(Colors.Red).append(Translatable.of("msg_warning_your_town_is_overclaimed"))); + if (town.isOverClaimed()) + TownyMessaging.sendPrefixedTownMessage(town, Translatable.literal(Colors.Red).append(Translatable.of("msg_warning_your_town_is_overclaimed"))); } /** @@ -312,10 +332,21 @@ public void onResidentPreJoinTown(TownPreAddResidentEvent event) { @EventHandler(ignoreCancelled = true) public void onResidentJoinTown(TownAddResidentEvent event) { + Town town = event.getTown(); + + if (town.getLevelNumber() > TownySettings.getTownLevelFromGivenInt(town.getNumResidents() - 1, town)) { + BukkitTools.fireEvent(new TownLevelIncreaseEvent(town)); + } + if (town.hasNation()) { + Nation nation = town.getNationOrNull(); + if (nation.getLevelNumber() > TownySettings.getNationLevelFromGivenInt(nation.getNumResidents() - 1)) { + BukkitTools.fireEvent(new NationLevelIncreaseEvent(nation)); + } + } + if (!TownySettings.isPromptingNewResidentsToTownSpawn() || !TownySettings.getBoolean(ConfigNodes.SPAWNING_ALLOW_TOWN_SPAWN)) return; - Town town = event.getTown(); Player player = event.getResident().getPlayer(); Town playerLocationTown = Optional.ofNullable(player).map(p -> TownyAPI.getInstance().getTown(p.getLocation())).orElse(null); @@ -362,4 +393,48 @@ private void attemptPlayerCacheReset(Player player, WorldCoord worldCoord) { return; Towny.getPlugin().resetCache(player); } + + /* + * Watch for town and nation level increasing/decreasing and reassign permissions in case the players have level-requirement permissions. + */ + + @EventHandler + public void onTownLevelIncrease(TownLevelIncreaseEvent event) { + if (!TownyPerms.ranksWithTownLevelRequirementPresent()) + return; + event.getTown().getResidents() + .stream() + .filter(Resident::isOnline) + .forEach(r -> TownyPerms.assignPermissions(r, r.getPlayer())); + } + + @EventHandler + public void onTownLevelDecrease(TownLevelDecreaseEvent event) { + if (!TownyPerms.ranksWithTownLevelRequirementPresent()) + return; + event.getTown().getResidents() + .stream() + .filter(Resident::isOnline) + .forEach(r -> TownyPerms.assignPermissions(r, r.getPlayer())); + } + + @EventHandler + public void onNationLevelIncrease(NationLevelIncreaseEvent event) { + if (!TownyPerms.ranksWithNationLevelRequirementPresent()) + return; + event.getNation().getResidents() + .stream() + .filter(Resident::isOnline) + .forEach(r -> TownyPerms.assignPermissions(r, r.getPlayer())); + } + + @EventHandler + public void onNationLevelDecrease(NationLevelDecreaseEvent event) { + if (!TownyPerms.ranksWithNationLevelRequirementPresent()) + return; + event.getNation().getResidents() + .stream() + .filter(Resident::isOnline) + .forEach(r -> TownyPerms.assignPermissions(r, r.getPlayer())); + } } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java index 4e83d6c1b2d..db4af6cdd42 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Resident.java @@ -593,9 +593,26 @@ public boolean hasTownRank(String rank) { } public List getTownRanks() { + if (!townRanks.isEmpty() && hasTown()) { + ArrayList out = new ArrayList<>(); + for (String rank : new ArrayList<>(townRanks)) { + int requiredTownLevelForRank = TownyPerms.getRankTownLevelReq(rank); + if (requiredTownLevelForRank == 0 || getTownOrNull().getLevelNumber() >= requiredTownLevelForRank) + out.add(rank); + } + // Return out modified list of ranks, so that the player will not lose ranks + // they've been assigned when their town goes down in a level temporarily. This + // ensures they save and load their proper list of ranks. + return Collections.unmodifiableList(out); + } return Collections.unmodifiableList(townRanks); } - + + @ApiStatus.Internal + public List getTownRanksForSaving() { + return Collections.unmodifiableList(townRanks); + } + public boolean removeTownRank(String rank) { if (hasTownRank(rank)) { @@ -646,6 +663,23 @@ public boolean hasNationRank(String rank) { } public List getNationRanks() { + if (!nationRanks.isEmpty() && hasNation()) { + ArrayList out = new ArrayList<>(); + for (String rank : new ArrayList<>(nationRanks)) { + int requiredNationLevelForRank = TownyPerms.getRankTownLevelReq(rank); + if (requiredNationLevelForRank == 0 || getNationOrNull().getLevelNumber() >= requiredNationLevelForRank) + out.add(rank); + } + // Return out modified list of ranks, so that the player will not lose ranks + // they've been assigned when their nation goes down in a level temporarily. This + // ensures they save and load their proper list of ranks. + return Collections.unmodifiableList(out); + } + return Collections.unmodifiableList(nationRanks); + } + + @ApiStatus.Internal + public List getNationRanksForSaving() { return Collections.unmodifiableList(nationRanks); } 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 832d86a0cf3..7da35007c82 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 @@ -58,6 +58,8 @@ public class TownyPerms { private static final HashMap residentPrefixMap = new HashMap<>(); private static final String RANKPRIORITY_PREFIX = "towny.rankpriority."; private static final String RANKPREFIX_PREFIX = "towny.rankprefix."; + private static final String RANK_TOWN_LEVEL_REQUIREMENT_PREFIX = "towny.town_level_requirement."; + private static final String RANK_NATION_LEVEL_REQUIREMENT_PREFIX = "towny.nation_level_requirement."; public static void initialize(Towny plugin) { TownyPerms.plugin = plugin; @@ -416,7 +418,7 @@ public static List getTownMayor() { */ public static List getTownRankPermissions(String rank) { - return getList("towns.ranks." + rank);//.toLowerCase()); + return getList("towns.ranks." + rank); } /* @@ -510,6 +512,7 @@ public static boolean hasConqueredNodes() { /* * Resident Primary Rank / Rank Prefix */ + public static String getResidentPrimaryRankPrefix(Resident resident) { return residentPrefixMap.getOrDefault(resident.getUUID(), setResidentPrimaryRankPrefix(resident)); } @@ -563,7 +566,7 @@ private static int getRankPriority(List nodes) { int topValue = 0; for (String node : nodes) { if (node.startsWith(RANKPRIORITY_PREFIX)) { - int priorityValue = getNodePriority(node); + int priorityValue = getNodePriority(node, RANKPRIORITY_PREFIX.length()); if (topValue >= priorityValue) continue; topValue = priorityValue; @@ -572,13 +575,50 @@ private static int getRankPriority(List nodes) { return topValue; } - private static int getNodePriority(String node) { + private static int getNodePriority(String node, int length) { try { - return Integer.parseInt(node.substring(RANKPRIORITY_PREFIX.length())); + return Integer.parseInt(node.substring(length)); } catch (NumberFormatException ignored) { return 0; } } + + /* + * TownLevel & NationLevel Rank Requirements + */ + + public static int getRankTownLevelReq(String rank) { + for (String node : getTownRankPermissions(rank)) + if (node.startsWith(RANK_TOWN_LEVEL_REQUIREMENT_PREFIX)) + return getNodePriority(node, RANK_TOWN_LEVEL_REQUIREMENT_PREFIX.length()); + return 0; + } + + public static int getRankNationLevelReq(String rank) { + for (String node : getNationRankPermissions(rank)) + if (node.startsWith(RANK_NATION_LEVEL_REQUIREMENT_PREFIX)) + return getNodePriority(node, RANK_NATION_LEVEL_REQUIREMENT_PREFIX.length()); + return 0; + } + + public static boolean ranksWithTownLevelRequirementPresent() { + for (String rank : getTownRanks()) { + for (String node : getTownRankPermissions(rank)) + if (node.startsWith(RANK_TOWN_LEVEL_REQUIREMENT_PREFIX)) + return true; + } + return false; + } + + public static boolean ranksWithNationLevelRequirementPresent() { + for (String rank : getNationRanks()) { + for (String node : getNationRankPermissions(rank)) + if (node.startsWith(RANK_NATION_LEVEL_REQUIREMENT_PREFIX)) + return true; + } + return false; + } + /* * Permission utility functions taken from GroupManager (which I wrote anyway). */ @@ -772,6 +812,24 @@ private static void buildComments() { "# - towny.rankpriority.100 #", "# - towny.rankprefix.&a<&2Sheriff&a> #", "# #", + "# The towns.ranks and nations.ranks sections support requiring their town or nation to have #", + "# a minimum town_level or nation_level. This means that you can lock ranks behind a town or #", + "# nation's population. By adding a permission node to a rank you will set this requirement: #", + "# You can read about town_levels and nation_levels here: https://tinyurl.com/3e9ahk2m #", + "# Ex: #", + "# - towny.town_level_requirement.4 #", + "# Adding this to a town rank will require the Town to have a town_level of 4 or more to be #", + "# able to assign that rank to their residents. If the town lost population the residents #", + "# with a rank beyond their town's town_level will have that rank removed from them. When #", + "# their town regains enough population, that rank will automatically be re-assigned to the #", + "# resident. When a town rank does not contain this node it will have no town_level #", + "# requirement. #", + "# Likewise, nation ranks support an optional nation_level requirement, Ex: #", + "# - towny.nation_level_requirement.4 #", + "# When added to a nation rank this rank will only be granted when a nation is of level 4 or #", + "# greater. When a nation rank does not include this node it will not require any #", + "# nation_level. #", + "# #", "#############################################################################################", "", "", @@ -812,5 +870,4 @@ private static void buildComments() { public static CommentedConfiguration getTownyPermsFile() { return perms; } - }