From f4e9a9835963298721f188fc109b2f006a51baf9 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 14:33:45 -0500 Subject: [PATCH 01/17] Backend object, database stuff. TODO: front end command stuff. --- .../bukkit/towny/TownyUniverse.java | 53 +++++++ .../bukkit/towny/db/SQLSchema.java | 11 ++ .../bukkit/towny/db/TownyDataSource.java | 37 ++++- .../bukkit/towny/db/TownyFlatFileSource.java | 111 ++++++++++++- .../bukkit/towny/db/TownySQLSource.java | 146 +++++++++++++++++- .../bukkit/towny/object/District.java | 112 ++++++++++++++ .../palmergames/bukkit/towny/object/Town.java | 48 ++++++ .../bukkit/towny/object/TownBlock.java | 22 +++ Towny/src/main/resources/lang/en-US.yml | 4 + 9 files changed, 539 insertions(+), 5 deletions(-) create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyUniverse.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyUniverse.java index 333e32bab5..f6139a403e 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyUniverse.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyUniverse.java @@ -10,6 +10,7 @@ import com.palmergames.bukkit.towny.exceptions.KeyAlreadyRegisteredException; import com.palmergames.bukkit.towny.exceptions.NotRegisteredException; import com.palmergames.bukkit.towny.exceptions.initialization.TownyInitException; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.PlotGroup; import com.palmergames.bukkit.towny.object.Position; @@ -89,6 +90,7 @@ public class TownyUniverse { private final Map jailUUIDMap = new ConcurrentHashMap<>(); private final Map replacementNamesMap = new ConcurrentHashMap<>(); private final Map plotGroupUUIDMap = new ConcurrentHashMap<>(); + private final Map districtUUIDMap = new ConcurrentHashMap<>(); private final Map wildernessMapDataMap = new ConcurrentHashMap(); private final String rootFolder; @@ -905,6 +907,57 @@ public PlotGroup getGroup(UUID groupID) { return plotGroupUUIDMap.get(groupID); } + /* + * District Stuff. + */ + + /** + * Used in loading only. + * @param uuid UUID to assign to the District. + */ + public void newDistrictInternal(UUID uuid) { + District district = new District(uuid, null, null); + registerDistrict(district); + } + + + public void registerDistrict(District district) { + districtUUIDMap.put(district.getUUID(), district); + } + + public void unregisterDistrict(UUID uuid) { + District district = districtUUIDMap.get(uuid); + if (district == null) + return; + district.getTown().removeDistrict(district); + districtUUIDMap.remove(uuid); + } + + /** + * Get all the districts from all towns + * Returns a collection that does not reflect any district additions/removals + * + * @return collection of District + */ + public Collection getDistricts() { + return new ArrayList<>(districtUUIDMap.values()); + } + + public Set getDistrictUUIDs() { + return districtUUIDMap.keySet(); + } + + /** + * Gets the district from the town name and the district UUID + * + * @param districtID UUID of the district + * @return District if found, null if none found. + */ + @Nullable + public District getDistrict(UUID districtID) { + return districtUUIDMap.get(districtID); + } + /* * Metadata Stuff */ 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 a625821048..7eb1825192 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 @@ -48,6 +48,9 @@ public static void initTables(Connection cntx) { initTable(cntx, TownyDBTableType.PLOTGROUP); updateTable(cntx, TownyDBTableType.PLOTGROUP, getPlotGroupColumns()); + initTable(cntx, TownyDBTableType.DISTRICT); + updateTable(cntx, TownyDBTableType.DISTRICT, getDistrictColumns()); + initTable(cntx, TownyDBTableType.JAIL); updateTable(cntx, TownyDBTableType.JAIL, getJailsColumns()); @@ -160,6 +163,14 @@ private static List getPlotGroupColumns() { return columns; } + private static List getDistrictColumns() { + List columns = new ArrayList<>(); + columns.add("`districtName` mediumtext NOT NULL"); + columns.add("`town` VARCHAR(32) NOT NULL"); + columns.add("`metadata` text DEFAULT NULL"); + return columns; + } + private static List getResidentColumns(){ List columns = new ArrayList<>(); columns.add("`town` mediumtext"); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java index 3a279f9164..792e0a9965 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java @@ -8,6 +8,7 @@ import com.palmergames.bukkit.towny.event.DeleteNationEvent; import com.palmergames.bukkit.towny.exceptions.AlreadyRegisteredException; import com.palmergames.bukkit.towny.exceptions.NotRegisteredException; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.PlotGroup; import com.palmergames.bukkit.towny.object.Resident; @@ -54,12 +55,12 @@ public abstract class TownyDataSource { public boolean loadAll() { - return loadWorldList() && loadNationList() && loadTownList() && loadPlotGroupList() && loadJailList() && loadResidentList() && loadTownBlockList() && loadWorlds() && loadResidents() && loadTowns() && loadNations() && loadTownBlocks() && loadPlotGroups() && loadJails() && loadRegenList() && loadCooldowns(); + return loadWorldList() && loadNationList() && loadTownList() && loadPlotGroupList() && loadDistrictList() && loadJailList() && loadResidentList() && loadTownBlockList() && loadWorlds() && loadResidents() && loadTowns() && loadNations() && loadTownBlocks() && loadPlotGroups() && loadDistricts() && loadJails() && loadRegenList() && loadCooldowns(); } public boolean saveAll() { - return saveWorlds() && saveNations() && saveTowns() && saveResidents() && savePlotGroups() && saveTownBlocks() && saveJails() && saveRegenList() && saveCooldowns(); + return saveWorlds() && saveNations() && saveTowns() && saveResidents() && savePlotGroups() && saveDistricts() && saveTownBlocks() && saveJails() && saveRegenList() && saveCooldowns(); } public boolean saveAllWorlds() { @@ -104,6 +105,10 @@ public boolean saveQueues() { abstract public boolean loadPlotGroup(PlotGroup group); + abstract public boolean loadDistrictList(); + + abstract public boolean loadDistrict(District district); + abstract public boolean saveRegenList(); abstract public boolean saveResident(Resident resident); @@ -114,6 +119,8 @@ public boolean saveQueues() { abstract public boolean savePlotGroup(PlotGroup group); + abstract public boolean saveDistrict(District district); + abstract public boolean saveJail(Jail jail); abstract public boolean saveNation(Nation nation); @@ -148,6 +155,8 @@ public boolean saveQueues() { abstract public void deletePlotGroup(PlotGroup group); + abstract public void deleteDistrict(District district); + abstract public void deleteJail(Jail jail); abstract public CompletableFuture> getHibernatedResidentRegistered(UUID uuid); @@ -226,6 +235,17 @@ public boolean loadPlotGroups() { return true; } + public boolean loadDistricts() { + TownyMessaging.sendDebugMsg("Loading Districts"); + for (District district : universe.getDistricts()) { + if (!loadDistrict(district)) { + plugin.getLogger().severe("Loading Error: Could not read District data: '" + district.getUUID() + "'."); + return false; + } + } + return true; + } + abstract public boolean loadCooldowns(); /* @@ -253,6 +273,19 @@ public boolean savePlotGroups() { return true; } + public boolean saveDistricts() { + TownyMessaging.sendDebugMsg("Saving Districts"); + for (District district : universe.getDistricts()) + /* + * Only save districts which actually have townblocks associated with them. + */ + if (district.hasTownBlocks()) + saveDistrict(district); + else + deleteDistrict(district); + return true; + } + public boolean saveJails() { TownyMessaging.sendDebugMsg("Saving Jails"); for (Jail jail : universe.getJails()) 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 2af736d86f..aa299b5fcd 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 @@ -18,6 +18,7 @@ import com.palmergames.bukkit.towny.exceptions.InvalidNameException; import com.palmergames.bukkit.towny.exceptions.NotRegisteredException; import com.palmergames.bukkit.towny.exceptions.TownyException; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.PermissionData; import com.palmergames.bukkit.towny.object.PlotGroup; @@ -137,6 +138,10 @@ public String getPlotGroupFilename(PlotGroup group) { return dataFolderPath + File.separator + "plotgroups" + File.separator + group.getUUID() + ".data"; } + public String getDistrictFilename(District district) { + return dataFolderPath + File.separator + "districts" + File.separator + district.getUUID() + ".data"; + } + public String getJailFilename(Jail jail) { return dataFolderPath + File.separator + "jails" + File.separator + jail.getUUID() + ".txt"; } @@ -217,6 +222,20 @@ public boolean loadPlotGroupList() { return true; } + + @Override + public boolean loadDistrictList() { + TownyMessaging.sendDebugMsg(Translation.of("flatfile_dbg_loading_district_list")); + File[] districtFiles = receiveObjectFiles("districts", ".data"); + + if (districtFiles == null) + return true; + + for (File districtFile : districtFiles) + universe.newDistrictInternal(UUID.fromString(districtFile.getName().replace(".data", ""))); + + return true; + } @Override public boolean loadResidentList() { @@ -1647,6 +1666,48 @@ public boolean loadPlotGroup(PlotGroup group) { return true; } + + public boolean loadDistrict(District district) { + String line = ""; + String path = getDistrictFilename(district); + + File districtFile = new File(path); + if (districtFile.exists() && districtFile.isFile()) { + try { + HashMap keys = FileMgmt.loadFileIntoHashMap(districtFile); + + line = keys.get("districtName"); + if (line != null) + district.setName(line.trim()); + + line = keys.get("town"); + if (line != null && !line.isEmpty()) { + Town town = universe.getTown(line.trim()); + if (town != null) { + district.setTown(town); + } else { + TownyMessaging.sendDebugMsg(Translation.of("flatfile_dbg_district_file_missing_town_delete", path)); + deleteDistrict(district); + TownyMessaging.sendDebugMsg(Translation.of("flatfile_dbg_missing_file_delete_district_entry", path)); + return true; + } + } else { + TownyMessaging.sendErrorMsg(Translation.of("flatfile_err_could_not_add_to_town")); + deleteDistrict(district); + } + + line = keys.get("metadata"); + if (line != null) + MetadataLoader.getInstance().deserializeMetadata(district, line.trim()); + + } catch (Exception e) { + TownyMessaging.sendErrorMsg(Translation.of("flatfile_err_exception_reading_district_file_at_line", path, line)); + return false; + } + } + + return true; + } @Override public boolean loadTownBlocks() { @@ -1795,6 +1856,21 @@ else if (universe.getReplacementNameMap().containsKey(line.trim())) } } + line = keys.get("districtID"); + UUID districtID = null; + if (line != null && !line.isEmpty()) { + districtID = UUID.fromString(line.trim()); + } + + if (districtID != null) { + District district = universe.getDistrict(districtID); + if (district != null) { + townBlock.setDistrict(district); + } else { + townBlock.removeDistrict(); + } + } + line = keys.get("trustedResidents"); if (line != null && !line.isEmpty() && townBlock.getTrustedResidents().isEmpty()) { for (Resident resident : TownyAPI.getInstance().getResidents(toUUIDArray(line.split(",")))) @@ -2129,6 +2205,24 @@ public boolean savePlotGroup(PlotGroup group) { return true; } + @Override + public boolean saveDistrict(District district) { + List list = new ArrayList<>(); + + try { + list.add("districtName=" + district.getName()); + list.add("town=" + district.getTown().getName()); + list.add("metadata=" + serializeMetadata(district)); + } catch (Exception e) { + plugin.getLogger().log(Level.WARNING, "An exception occurred while saving district " + Optional.ofNullable(district).map(g -> g.getUUID().toString()).orElse("null") + ": ", e); + } + + // Save file + this.queryQueue.add(new FlatFileSaveTask(list, getDistrictFilename(district))); + + return true; + } + @Override public boolean saveNation(Nation nation) { @@ -2415,6 +2509,14 @@ public boolean saveTownBlock(TownBlock townBlock) { } list.add("groupID=" + groupID); + + // District ID + StringBuilder districtID = new StringBuilder(); + if (townBlock.hasDistrict()) { + districtID.append(townBlock.getDistrict().getUUID()); + } + + list.add("districtID=" + districtID); list.add("trustedResidents=" + StringMgmt.join(toUUIDList(townBlock.getTrustedResidents()), ",")); @@ -2504,7 +2606,13 @@ public void deletePlotGroup(PlotGroup group) { File file = new File(getPlotGroupFilename(group)); queryQueue.add(new DeleteFileTask(file, false)); } - + + @Override + public void deleteDistrict(District district) { + File file = new File(getDistrictFilename(district)); + queryQueue.add(new DeleteFileTask(file, false)); + } + @Override public void deleteJail(Jail jail) { File file = new File(getJailFilename(jail)); @@ -2572,4 +2680,5 @@ public boolean saveCooldowns() { return true; } + } 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 3d7109dd3d..2cdac7ca35 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 @@ -18,6 +18,7 @@ import com.palmergames.bukkit.towny.exceptions.NotRegisteredException; import com.palmergames.bukkit.towny.exceptions.TownyException; import com.palmergames.bukkit.towny.exceptions.initialization.TownyInitException; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.PermissionData; import com.palmergames.bukkit.towny.object.PlotGroup; @@ -422,6 +423,7 @@ public boolean cleanup() { public enum TownyDBTableType { JAIL("JAILS", "SELECT uuid FROM ", "uuid"), PLOTGROUP("PLOTGROUPS", "SELECT groupID FROM ", "groupID"), + DISTRICT("DISTRICTS", "SELECT districtID FROM ", "districtID"), RESIDENT("RESIDENTS", "SELECT name FROM ", "name"), HIBERNATED_RESIDENT("HIBERNATEDRESIDENTS", "", "uuid"), TOWN("TOWNS", "SELECT name FROM ", "name"), @@ -627,7 +629,32 @@ public boolean loadPlotGroupList() { return false; } - + + @Override + public boolean loadDistrictList() { + TownyMessaging.sendDebugMsg("Loading District List"); + + try (Connection connection = getConnection(); + Statement s = connection.createStatement(); + ResultSet rs = s.executeQuery("SELECT districtID FROM " + tb_prefix + "DISTRICTS")) { + + while (rs.next()) { + try { + universe.newDistrictInternal(UUID.fromString(rs.getString("districtID"))); + } catch (IllegalArgumentException e) { + plugin.getLogger().log(Level.WARNING, "ID for district is not a valid uuid, skipped loading district {}", rs.getString("districtID")); + } + } + + return true; + + } catch (SQLException e) { + plugin.getLogger().log(Level.SEVERE, "An exception occurred while loading district list", e); + } + + return false; + } + public boolean loadJailList() { TownyMessaging.sendDebugMsg("Loading Jail List"); @@ -1896,6 +1923,22 @@ public boolean loadTownBlocks() { } catch (SQLException ignored) { } + try { + line = rs.getString("groupID"); + if (line != null && !line.isEmpty()) { + try { + UUID districtID = UUID.fromString(line.trim()); + District district = universe.getDistrict(districtID); + if (district != null) { + townBlock.setDistrict(district); + } + } catch (Exception ignored) { + } + + } + } catch (SQLException ignored) { + } + line = rs.getString("trustedResidents"); if (line != null && !line.isEmpty() && townBlock.getTrustedResidents().isEmpty()) { String search = (line.contains("#")) ? "#" : ","; @@ -1959,6 +2002,27 @@ public boolean loadPlotGroups() { return true; } + @Override + public boolean loadDistricts() { + TownyMessaging.sendDebugMsg("Loading districts."); + + try (Connection connection = getConnection(); + Statement s = connection.createStatement(); + ResultSet rs = s.executeQuery("SELECT * FROM " + tb_prefix + "DISTRICTS ")) { + while (rs.next()) { + if (!loadDistrict(rs)) { + plugin.getLogger().warning("Loading Error: Could not read district data properly."); + return false; + } + } + } catch (SQLException e) { + TownyMessaging.sendErrorMsg("SQL: Load District sql Error - " + e.getMessage()); + return false; + } + + return true; + } + @Override public boolean loadCooldowns() { try (Connection connection = getConnection(); @@ -2043,6 +2107,54 @@ public boolean loadPlotGroup(PlotGroup group) { return true; } + private boolean loadDistrict(ResultSet rs) { + String line = null; + String uuid = null; + + try { + PlotGroup district = universe.getGroup(UUID.fromString(rs.getString("districtID"))); + if (district == null) { + TownyMessaging.sendErrorMsg("SQL: A district was not registered properly on load!"); + return true; + } + uuid = district.getUUID().toString(); + + line = rs.getString("districtName"); + if (line != null) + try { + district.setName(line.trim()); + } catch (Exception ignored) { + } + + line = rs.getString("town"); + if (line != null) { + Town town = universe.getTown(line.trim()); + if (town != null) { + district.setTown(town); + } else { + deletePlotGroup(district); + return true; + } + } + + line = rs.getString("metadata"); + if (line != null) { + MetadataLoader.getInstance().deserializeMetadata(district, line); + } + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "Loading Error: Exception while reading district: " + uuid + + " at line: " + line + " in the sql database", e); + return false; + } + return true; + } + + @Override + public boolean loadDistrict(District district) { + // Unused in SQL. + return true; + } + @Override public boolean loadJails() { TownyMessaging.sendDebugMsg("Loading Jails"); @@ -2314,6 +2426,24 @@ public synchronized boolean savePlotGroup(PlotGroup group) { return false; } + @Override + public boolean saveDistrict(District district) { + TownyMessaging.sendDebugMsg("Saving district " + district.getName()); + try { + HashMap pltgrp_hm = new HashMap<>(); + pltgrp_hm.put("districtID", district.getUUID().toString()); + pltgrp_hm.put("districtName", district.getName()); + pltgrp_hm.put("town", district.getTown().getName()); + pltgrp_hm.put("metadata", serializeMetadata(district)); + + updateDB("DISTRICTS", pltgrp_hm, Collections.singletonList("districtID")); + + } catch (Exception e) { + plugin.getLogger().log(Level.WARNING, "SQL: Save Districts unknown error", e); + } + return false; + } + @Override public synchronized boolean saveNation(Nation nation) { @@ -2514,6 +2644,10 @@ public synchronized boolean saveTownBlock(TownBlock townBlock) { tb_hm.put("groupID", townBlock.getPlotObjectGroup().getUUID().toString()); else tb_hm.put("groupID", ""); + if (townBlock.hasDistrict()) + tb_hm.put("districtID", townBlock.getDistrict().getUUID().toString()); + else + tb_hm.put("districtID", ""); if (townBlock.hasMeta()) tb_hm.put("metadata", serializeMetadata(townBlock)); else @@ -2619,7 +2753,14 @@ public void deletePlotGroup(PlotGroup group) { pltgrp_hm.put("groupID", group.getUUID()); DeleteDB("PLOTGROUPS", pltgrp_hm); } - + + @Override + public void deleteDistrict(District district) { + HashMap district_hm = new HashMap<>(); + district_hm.put("districtID", district.getUUID()); + DeleteDB("DISTRICTS", district_hm); + } + @Override public void deleteJail(Jail jail) { @@ -2649,4 +2790,5 @@ public CompletableFuture> getHibernatedResidentRegistered(UUID uu public HikariDataSource getHikariDataSource() { return hikariDataSource; } + } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java new file mode 100644 index 0000000000..15c2e9fd10 --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java @@ -0,0 +1,112 @@ +package com.palmergames.bukkit.towny.object; + +import com.palmergames.bukkit.towny.TownyMessaging; +import com.palmergames.bukkit.towny.TownyUniverse; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +/** + * @author LlmDl + */ +public class District extends ObjectGroup implements Nameable, Savable { + private List townBlocks; + private Town town; + + /** + * @param id A unique identifier for the district id. + * @param name An alias for the id used for player in-game interaction via commands. + * @param town The town that this district is owned by. + */ + public District(UUID id, String name, Town town) { + super(id, name); + this.town = town; + } + + /** + * Store district in format "name,id,town,price" + * @return The string in the format described. + */ + @Override + public String toString() { + return super.toString() + "," + getTown().toString(); + } + + @Override + public boolean exists() { + return this.town != null && this.town.exists() && this.town.hasDistrictName(getName()); + } + + /** + * Override the name change method to internally rehash the district map. + * @param name The name of the district. + */ + @Override + public void setName(String name) { + if (getName() == null) { + super.setName(name); + } + else { + String oldName = getName(); + super.setName(name); + town.renameDistrict(oldName, this); + } + } + + public void setTown(Town town) { + this.town = town; + + try { + town.addDistrict(this); + } catch (Exception e) { + TownyMessaging.sendErrorMsg(e.getMessage()); + } + } + + public Town getTown() { + return town; + } + + /** + * + * @return The qualified resident mode string. + */ + public String toModeString() { + return "District{" + this.toString() + "}"; + } + + public void addTownBlock(TownBlock townBlock) { + if (townBlocks == null) + townBlocks = new ArrayList<>(); + + townBlocks.add(townBlock); + } + + public void removeTownBlock(TownBlock townBlock) { + if (townBlocks != null) + townBlocks.remove(townBlock); + } + + public void setTownblocks(List townBlocks) { + this.townBlocks = townBlocks; + } + + public Collection getTownBlocks() { + return Collections.unmodifiableCollection(townBlocks); + } + + public boolean hasTownBlocks() { + return townBlocks != null && !townBlocks.isEmpty(); + } + + public boolean hasTownBlock(TownBlock townBlock) { + return townBlocks.contains(townBlock); + } + + @Override + public void save() { + TownyUniverse.getInstance().getDataSource().saveDistrict(this); + } +} 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 10998c72b5..f7316a46be 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 @@ -81,6 +81,7 @@ public class Town extends Government implements TownBlockOwner { private List jails = null; private HashMap plotGroups = null; private TownBlockTypeCache plotTypeCache = new TownBlockTypeCache(); + private HashMap districts = null; private Resident mayor; private String founderName; @@ -1444,6 +1445,53 @@ public PlotGroup getPlotObjectGroupFromName(String name) { return null; } + public void renameDistrict(String oldName, District district) { + districts.remove(oldName); + districts.put(district.getName(), district); + } + + public void addDistrict(District district) { + if (!hasDistricts()) + districts = new HashMap<>(); + + districts.put(district.getName(), district); + } + + public void removeDistrict(District district) { + if (hasDistricts() && districts.remove(district.getName()) != null) { + for (TownBlock tb : new ArrayList<>(district.getTownBlocks())) { + if (tb.hasDistrict() && tb.getDistrict().getUUID().equals(district.getUUID())) { + district.removeTownBlock(tb); + tb.removeDistrict(); + tb.save(); + } + } + } + } + + // Abstract to collection in case we want to change structure in the future + public Collection getDistricts() { + if (districts == null || districts.isEmpty()) + return Collections.emptyList(); + + return Collections.unmodifiableCollection(districts.values()); + } + + public boolean hasDistricts() { + return districts != null; + } + + public boolean hasDistrictName(String name) { + return hasDistricts() && districts.containsKey(name); + } + + @Nullable + public District getDistrictFromName(String name) { + if (hasDistricts() && hasDistrictName(name)) + return districts.get(name); + return null; + } + @Override public double getBankCap() { return TownySettings.getTownBankCap(this); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/TownBlock.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/TownBlock.java index d0ed30a35f..f45bf7272d 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/TownBlock.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/TownBlock.java @@ -45,6 +45,7 @@ public class TownBlock extends TownyObject { private boolean taxed = true; private boolean outpost = false; private PlotGroup plotGroup; + private District district; private long claimedAt; private Jail jail; private Map permissionOverrides = new HashMap<>(); @@ -561,6 +562,27 @@ public void setPlotObjectGroup(PlotGroup group) { } } + + public boolean hasDistrict() { return district != null; } + + public District getDistrict() { + return district; + } + + public void removeDistrict() { + this.district = null; + } + + public void setDistrict(District district) { + this.district = district; + + try { + district.addTownBlock(this); + } catch (NullPointerException e) { + TownyMessaging.sendErrorMsg("Townblock failed to setDistrict(district), district is null. "); + } + } + @Override public void save() { TownyUniverse.getInstance().getDataSource().saveTownBlock(this); diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index 7f74bec6fa..99ea6ffb62 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -1545,6 +1545,10 @@ flatfile_dbg_deleting_duplicate: 'Deleting: %s which is a duplicate of %s' flatfile_dbg_folders_found: 'Folders found %s' flatfile_dbg_group_file_missing_town_delete: 'Group file missing Town, deleting %s' flatfile_dbg_loading_group_list: 'Loading Group list...' +flatfile_dbg_district_file_missing_town_delete: 'District file missing Town, deleting %s' +flatfile_dbg_missing_file_delete_district_entry: 'Missing file: %s deleting entry in [districtuuid].data' +flatfile_err_exception_reading_district_file_at_line: 'Loading Error: Exception while reading District file %s at line: %s' +flatfile_dbg_loading_district_list: 'Loading District list...' flatfile_dbg_loading_nation: 'Loading Nation: %s' flatfile_dbg_loading_nation_list: 'Loading Nation list...' flatfile_dbg_loading_resident: 'Loading Resident: %s' From 3f3a354edd0d5f85a414e4b2d14a2d0b15b4fce1 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 14:40:50 -0500 Subject: [PATCH 02/17] Switch to saving/loading the town from/to a UUID, because that would be more sane. --- .../palmergames/bukkit/towny/db/SQLSchema.java | 2 +- .../bukkit/towny/db/TownyFlatFileSource.java | 9 +++++++-- .../bukkit/towny/db/TownySQLSource.java | 15 ++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) 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 7eb1825192..848a8f17d2 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 @@ -166,7 +166,7 @@ private static List getPlotGroupColumns() { private static List getDistrictColumns() { List columns = new ArrayList<>(); columns.add("`districtName` mediumtext NOT NULL"); - columns.add("`town` VARCHAR(32) NOT NULL"); + columns.add("`town` VARCHAR(36) NOT NULL"); columns.add("`metadata` text 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 aa299b5fcd..0a18bb5f66 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 @@ -1682,7 +1682,12 @@ public boolean loadDistrict(District district) { line = keys.get("town"); if (line != null && !line.isEmpty()) { - Town town = universe.getTown(line.trim()); + UUID uuid = UUID.fromString(line.trim()); + if (uuid == null) { + TownyMessaging.sendDebugMsg(Translation.of("flatfile_dbg_missing_file_delete_district_entry", path)); + return true; + } + Town town = universe.getTown(uuid); if (town != null) { district.setTown(town); } else { @@ -2211,7 +2216,7 @@ public boolean saveDistrict(District district) { try { list.add("districtName=" + district.getName()); - list.add("town=" + district.getTown().getName()); + list.add("town=" + district.getTown().getUUID().toString()); list.add("metadata=" + serializeMetadata(district)); } catch (Exception e) { plugin.getLogger().log(Level.WARNING, "An exception occurred while saving district " + Optional.ofNullable(district).map(g -> g.getUUID().toString()).orElse("null") + ": ", e); 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 2cdac7ca35..2fa1644b84 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 @@ -2109,7 +2109,7 @@ public boolean loadPlotGroup(PlotGroup group) { private boolean loadDistrict(ResultSet rs) { String line = null; - String uuid = null; + String uuidString = null; try { PlotGroup district = universe.getGroup(UUID.fromString(rs.getString("districtID"))); @@ -2117,7 +2117,7 @@ private boolean loadDistrict(ResultSet rs) { TownyMessaging.sendErrorMsg("SQL: A district was not registered properly on load!"); return true; } - uuid = district.getUUID().toString(); + uuidString = district.getUUID().toString(); line = rs.getString("districtName"); if (line != null) @@ -2128,7 +2128,12 @@ private boolean loadDistrict(ResultSet rs) { line = rs.getString("town"); if (line != null) { - Town town = universe.getTown(line.trim()); + UUID uuid = UUID.fromString(line.trim()); + if (uuid == null) { + deletePlotGroup(district); + return true; + } + Town town = universe.getTown(uuid); if (town != null) { district.setTown(town); } else { @@ -2142,7 +2147,7 @@ private boolean loadDistrict(ResultSet rs) { MetadataLoader.getInstance().deserializeMetadata(district, line); } } catch (SQLException e) { - plugin.getLogger().log(Level.WARNING, "Loading Error: Exception while reading district: " + uuid + plugin.getLogger().log(Level.WARNING, "Loading Error: Exception while reading district: " + uuidString + " at line: " + line + " in the sql database", e); return false; } @@ -2433,7 +2438,7 @@ public boolean saveDistrict(District district) { HashMap pltgrp_hm = new HashMap<>(); pltgrp_hm.put("districtID", district.getUUID().toString()); pltgrp_hm.put("districtName", district.getName()); - pltgrp_hm.put("town", district.getTown().getName()); + pltgrp_hm.put("town", district.getTown().getUUID().toString()); pltgrp_hm.put("metadata", serializeMetadata(district)); updateDB("DISTRICTS", pltgrp_hm, Collections.singletonList("districtID")); From 765d3adc7c88cea1159f39ebb4861d5da767958d Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 14:45:09 -0500 Subject: [PATCH 03/17] Reduce diff and delete unloadable district when it should. --- .../palmergames/bukkit/towny/db/TownyFlatFileSource.java | 6 +++--- .../com/palmergames/bukkit/towny/db/TownySQLSource.java | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) 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 0a18bb5f66..4fd521c58b 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 @@ -1685,6 +1685,7 @@ public boolean loadDistrict(District district) { UUID uuid = UUID.fromString(line.trim()); if (uuid == null) { TownyMessaging.sendDebugMsg(Translation.of("flatfile_dbg_missing_file_delete_district_entry", path)); + deleteDistrict(district); return true; } Town town = universe.getTown(uuid); @@ -2522,7 +2523,7 @@ public boolean saveTownBlock(TownBlock townBlock) { } list.add("districtID=" + districtID); - + list.add("trustedResidents=" + StringMgmt.join(toUUIDList(townBlock.getTrustedResidents()), ",")); Map stringMap = new HashMap<>(); @@ -2611,7 +2612,7 @@ public void deletePlotGroup(PlotGroup group) { File file = new File(getPlotGroupFilename(group)); queryQueue.add(new DeleteFileTask(file, false)); } - + @Override public void deleteDistrict(District district) { File file = new File(getDistrictFilename(district)); @@ -2685,5 +2686,4 @@ public boolean saveCooldowns() { return true; } - } 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 2fa1644b84..e13ed55290 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 @@ -629,7 +629,7 @@ public boolean loadPlotGroupList() { return false; } - + @Override public boolean loadDistrictList() { TownyMessaging.sendDebugMsg("Loading District List"); @@ -2758,7 +2758,7 @@ public void deletePlotGroup(PlotGroup group) { pltgrp_hm.put("groupID", group.getUUID()); DeleteDB("PLOTGROUPS", pltgrp_hm); } - + @Override public void deleteDistrict(District district) { HashMap district_hm = new HashMap<>(); @@ -2795,5 +2795,4 @@ public CompletableFuture> getHibernatedResidentRegistered(UUID uu public HikariDataSource getHikariDataSource() { return hikariDataSource; } - } From 2ec0f4c44d81301f5e06034ee6d3fdb8ddb559d5 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 15:30:55 -0500 Subject: [PATCH 04/17] Add /plot district command. TODO: Make adjacent townblocks rules apply for adding and removing to/from the district --- .../bukkit/towny/command/HelpMenu.java | 11 ++ .../bukkit/towny/command/PlotCommand.java | 185 ++++++++++++++++++ .../bukkit/towny/db/TownyDataSource.java | 4 + .../bukkit/towny/db/TownyDatabaseHandler.java | 16 ++ .../event/plot/district/DistrictAddEvent.java | 50 +++++ .../plot/district/DistrictCreatedEvent.java | 24 +++ .../plot/district/DistrictDeletedEvent.java | 67 +++++++ .../bukkit/towny/object/Resident.java | 13 ++ .../towny/permissions/PermissionNodes.java | 7 + .../bukkit/util/NameValidation.java | 12 ++ Towny/src/main/resources/lang/en-US.yml | 31 ++- 11 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictAddEvent.java create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictCreatedEvent.java create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictDeletedEvent.java 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 fd4047c150..96c5d16fea 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 @@ -827,6 +827,17 @@ protected MenuBuilder load() { .add(Translatable.of("msg_nfs_abr")); } }, + + PLOT_DISTRICT_HELP { + @Override + protected MenuBuilder load() { + return new MenuBuilder("plot district") + .add("add|new|create [name]", Translatable.of("plot_district_help_0")) + .add("remove", Translatable.of("plot_district_help_1")) + .add("delete", Translatable.of("plot_district_help_2")) + .add("rename [newName]", Translatable.of("plot_district_help_3")); + } + }, PLOT_GROUP_HELP { @Override 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 2096221ecc..9c4ac8478f 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 @@ -21,6 +21,9 @@ import com.palmergames.bukkit.towny.event.plot.PlotSetForSaleEvent; import com.palmergames.bukkit.towny.event.plot.PlotTrustAddEvent; import com.palmergames.bukkit.towny.event.plot.PlotTrustRemoveEvent; +import com.palmergames.bukkit.towny.event.plot.district.DistrictAddEvent; +import com.palmergames.bukkit.towny.event.plot.district.DistrictCreatedEvent; +import com.palmergames.bukkit.towny.event.plot.district.DistrictDeletedEvent; import com.palmergames.bukkit.towny.event.plot.group.PlotGroupAddEvent; import com.palmergames.bukkit.towny.event.plot.group.PlotGroupCreatedEvent; import com.palmergames.bukkit.towny.event.plot.group.PlotGroupDeletedEvent; @@ -35,6 +38,7 @@ import com.palmergames.bukkit.towny.exceptions.TownyException; import com.palmergames.bukkit.towny.huds.HUDManager; import com.palmergames.bukkit.towny.object.Coord; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.PermissionData; import com.palmergames.bukkit.towny.object.PlotGroup; import com.palmergames.bukkit.towny.object.Resident; @@ -105,6 +109,7 @@ public class PlotCommand extends BaseCommand implements CommandExecutor { "toggle", "clear", "group", + "district", "jailcell", "trust" ); @@ -123,6 +128,13 @@ public class PlotCommand extends BaseCommand implements CommandExecutor { "trust" ); + private static final List districtTabCompletes = Arrays.asList( + "add", + "delete", + "remove", + "rename" + ); + private static final List plotSetTabCompletes = Arrays.asList( "reset", "shop", @@ -240,6 +252,11 @@ else if (args.length == 3) default: return Collections.emptyList(); } + case "district": + if (args.length == 2) + return NameUtil.filterByStart(districtTabCompletes, args[1]); + if (args.length < 2) + break; case "group": if (args.length == 2) return NameUtil.filterByStart(plotGroupTabCompletes, args[1]); @@ -312,6 +329,7 @@ public void parsePlotCommand(Player player, String[] split) throws TownyExceptio case "evict" -> parsePlotEvict(resident, townBlock); case "fs", "forsale" -> parsePlotForSale(player, StringMgmt.remFirstArg(split), resident, townBlock); case "group" -> parsePlotGroup(StringMgmt.remFirstArg(split), resident, townBlock, player); + case "district" -> parseDistrict(StringMgmt.remFirstArg(split), resident, townBlock, player); case "info" -> sendPlotInfo(player, StringMgmt.remFirstArg(split)); case "jailcell" -> parsePlotJailCell(player, resident, townBlock, StringMgmt.remFirstArg(split)); case "nfs", "notforsale" -> parsePlotNotForSale(player, StringMgmt.remFirstArg(split), resident, townBlock); @@ -1255,6 +1273,165 @@ private void toggleTest(Player player, TownBlock townBlock, String split) throws } } + private void parseDistrict(String[] split, Resident resident, TownBlock townBlock, Player player) throws TownyException { + + Town town = townBlock.getTownOrNull(); + if (town == null) + throw new TownyException(Translatable.of("msg_not_claimed_1")); + + // Test we are allowed to work on this plot + // If this fails it will trigger a TownyException. + TownyAPI.getInstance().testPlotOwnerOrThrow(resident, townBlock); + + if (split.length <= 0 || split[0].equalsIgnoreCase("?")) { + HelpMenu.PLOT_DISTRICT_HELP.send(player); + if (townBlock.hasDistrict()) + TownyMessaging.sendMsg(player, Translatable.of("status_district_name_and_size", townBlock.getDistrict().getName(), townBlock.getDistrict().getTownBlocks().size())); + return; + } + + switch (split[0].toLowerCase(Locale.ROOT)) { + case "add", "new", "create" -> parseDistrictAdd(split, townBlock, player, town); + case "delete" -> parseDistrictDelete(townBlock, player, town); + case "remove" -> parseDistrictRemove(townBlock, player, town); + case "rename" -> parseDistrictRename(split, townBlock, player); + default -> { + HelpMenu.PLOT_DISTRICT_HELP.send(player); + if (townBlock.hasPlotObjectGroup()) + TownyMessaging.sendMsg(player, Translatable.of("status_district_name_and_size", townBlock.getDistrict().getName(), townBlock.getDistrict().getTownBlocks().size())); + } + } + } + + public void parseDistrictAdd(String[] split, TownBlock townBlock, Player player, Town town) throws TownyException { + checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_PLOT_DISTRICT_ADD.getNode()); + + Resident resident = getResidentOrThrow(player); + + if (split.length != 2 && !resident.hasDistrictName()) + throw new TownyException(Translatable.of("msg_err_plot_group_name_required")); + + String districtName = split.length == 2 + ? NameValidation.checkAndFilterDistrictNameOrThrow(split[1]) + : resident.hasDistrictName() + ? resident.getDistrictName() + : null; + + if (townBlock.hasDistrict()) { + // Already has a District and it is the same name being used to re-add. + if (townBlock.getDistrict().getName().equalsIgnoreCase(districtName)) + throw new TownyException(Translatable.of("msg_err_this_plot_is_already_part_of_the_plot_group_x", districtName)); + + final String name = districtName; + // Already has a PlotGroup, ask if they want to transfer from one group to another. + Confirmation.runOnAccept( ()-> { + District oldDistrict = townBlock.getDistrict(); + oldDistrict.removeTownBlock(townBlock); + if (oldDistrict.getTownBlocks().isEmpty() && !BukkitTools.isEventCancelled(new DistrictDeletedEvent(oldDistrict, player, DistrictDeletedEvent.Cause.NO_TOWNBLOCKS))) { + String oldName = oldDistrict.getName(); + town.removeDistrict(oldDistrict); + TownyUniverse.getInstance().getDataSource().removeDistrict(oldDistrict); + TownyMessaging.sendMsg(player, Translatable.of("msg_district_deleted", oldName)); + } else + oldDistrict.save(); + + try { + createOrAddOnToDistrict(townBlock, town, player, name); + resident.setDistrictName(name); + TownyMessaging.sendMsg(player, Translatable.of("msg_townblock_transferred_from_x_to_x_district", oldDistrict.getName(), townBlock.getDistrict().getName())); + } catch (TownyException e) { + TownyMessaging.sendErrorMsg(player, e.getMessage(player)); + } + }) + .setTitle(Translatable.of("msg_plot_group_already_exists_did_you_want_to_transfer", townBlock.getPlotObjectGroup().getName(), split[1])) + .sendTo(player); + } else { + // Create a brand new plot group. + createOrAddOnToDistrict(townBlock, town, player, districtName); + resident.setDistrictName(districtName); + TownyMessaging.sendMsg(player, Translatable.of("msg_plot_was_put_into_group_x", townBlock.getX(), townBlock.getZ(), townBlock.getDistrict().getName())); + } + } + + private void createOrAddOnToDistrict(TownBlock townBlock, Town town, Player player, String districtName) throws TownyException { + District newDistrict; + + // Don't add the group to the town data if it's already there. + if (town.hasDistrictName(districtName)) { + newDistrict = town.getDistrictFromName(districtName); + + BukkitTools.ifCancelledThenThrow(new DistrictAddEvent(newDistrict, townBlock, player)); + + } else { + // This is a brand new PlotGroup, register it. + newDistrict = new District(UUID.randomUUID(), districtName, town); + + BukkitTools.ifCancelledThenThrow(new DistrictCreatedEvent(newDistrict, townBlock, player)); + + TownyUniverse.getInstance().registerDistrict(newDistrict); + } + + // Add group to townblock, this also adds the townblock to the group. + townBlock.setDistrict(newDistrict); + + // Add the plot group to the town set. + town.addDistrict(newDistrict); + + // Save changes. + newDistrict.save(); + townBlock.save(); + } + public void parseDistrictDelete(TownBlock townBlock, Player player, Town town) throws TownyException { + checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_PLOT_DISTRICT_DELETE.getNode()); + + District district = catchMissingDistrict(townBlock); + + Confirmation.runOnAccept(()-> { + String name = district.getName(); + if (!BukkitTools.isEventCancelled(new DistrictDeletedEvent(district, player, DistrictDeletedEvent.Cause.DELETED))) { + town.removeDistrict(district); + TownyUniverse.getInstance().getDataSource().removeDistrict(district); + TownyMessaging.sendMsg(player, Translatable.of("msg_district_deleted", name)); + } + }).sendTo(player); + } + + public void parseDistrictRemove(TownBlock townBlock, Player player, Town town) throws TownyException { + checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_PLOT_DISTRICT_REMOVE.getNode()); + + District district = catchMissingDistrict(townBlock); + String name = district.getName(); + // Remove the plot from the district. + district.removeTownBlock(townBlock); + + // Detach district from townblock. + townBlock.removeDistrict(); + + // Save + townBlock.save(); + TownyMessaging.sendMsg(player, Translatable.of("msg_plot_was_removed_from_district_x", townBlock.getX(), townBlock.getZ(), name)); + + if (district.getTownBlocks().isEmpty() && !BukkitTools.isEventCancelled(new DistrictDeletedEvent(district, player, DistrictDeletedEvent.Cause.NO_TOWNBLOCKS))) { + town.removeDistrict(district); + TownyUniverse.getInstance().getDataSource().removeDistrict(district); + TownyMessaging.sendMsg(player, Translatable.of("msg_district_empty_deleted", name)); + } + } + + public void parseDistrictRename(String[] split, TownBlock townBlock, Player player) throws TownyException, AlreadyRegisteredException { + checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_PLOT_DISTRICT_RENAME.getNode()); + + if (split.length == 1) + throw new TownyException(Translatable.of("msg_err_plot_group_name_required")); + + District district = catchMissingDistrict(townBlock); + String newName= split[1]; + String oldName = district.getName(); + // Change name; + TownyUniverse.getInstance().getDataSource().renameDistrict(district, newName); + TownyMessaging.sendMsg(player, Translatable.of("msg_district_renamed_from_x_to_y", oldName, newName)); + } + private void parsePlotGroup(String[] split, Resident resident, TownBlock townBlock, Player player) throws TownyException { Town town = townBlock.getTownOrNull(); @@ -2072,4 +2249,12 @@ private PlotGroup catchMissingPlotGroup(TownBlock townBlock) throws TownyExcepti return townBlock.getPlotObjectGroup(); } + + private District catchMissingDistrict(TownBlock townBlock) throws TownyException { + // Make sure that the player is in a plotgroup. + if (!townBlock.hasDistrict()) + throw new TownyException(Translatable.of("msg_err_plot_not_associated_with_a_district")); + + return townBlock.getDistrict(); + } } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java index 792e0a9965..47fb4c61b3 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/db/TownyDataSource.java @@ -367,6 +367,8 @@ public boolean removeTown(@NotNull Town town, @NotNull DeleteTownEvent.Cause cau abstract public void removeJail(Jail jail); abstract public void removePlotGroup(PlotGroup group); + + abstract public void removeDistrict(District district); abstract public void renameTown(Town town, String newName) throws AlreadyRegisteredException, NotRegisteredException; @@ -380,6 +382,8 @@ public boolean removeTown(@NotNull Town town, @NotNull DeleteTownEvent.Cause cau abstract public void renameGroup(PlotGroup group, String newName) throws AlreadyRegisteredException; + abstract public void renameDistrict(District district, String newName) throws AlreadyRegisteredException; + /** * @deprecated since 0.100.2.9 use {@link #removeTown(Town, com.palmergames.bukkit.towny.event.DeleteTownEvent.Cause)} instead. * @param town 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 5ed7bac07c..8bb21720e5 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 @@ -24,6 +24,7 @@ import com.palmergames.bukkit.towny.exceptions.TownyException; import com.palmergames.bukkit.towny.invites.Invite; import com.palmergames.bukkit.towny.invites.InviteHandler; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.PlotGroup; import com.palmergames.bukkit.towny.object.Resident; @@ -585,6 +586,12 @@ public void removePlotGroup(PlotGroup group) { deletePlotGroup(group); } + @Override + public void removeDistrict(District district) { + universe.unregisterDistrict(district.getUUID()); + deleteDistrict(district); + } + /* * Rename Object Methods */ @@ -783,6 +790,15 @@ public void renameGroup(PlotGroup group, String newName) throws AlreadyRegistere savePlotGroup(group); } + @Override + public void renameDistrict(District district, String newName) throws AlreadyRegisteredException { + // Create new one + district.setName(newName); + + // Save + saveDistrict(district); + } + @Override public void renamePlayer(Resident resident, String newName) throws AlreadyRegisteredException, NotRegisteredException { diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictAddEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictAddEvent.java new file mode 100644 index 0000000000..05557c97ff --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictAddEvent.java @@ -0,0 +1,50 @@ +package com.palmergames.bukkit.towny.event.plot.district; + +import com.palmergames.bukkit.towny.event.CancellableTownyEvent; +import com.palmergames.bukkit.towny.object.District; +import com.palmergames.bukkit.towny.object.TownBlock; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a townblock is added into a district + */ +public class DistrictAddEvent extends CancellableTownyEvent { + private static final HandlerList HANDLER_LIST = new HandlerList(); + private final District district; + private final TownBlock townBlock; + private final Player player; + + public DistrictAddEvent(final District district, final TownBlock townBlock, final Player player) { + this.district = district; + this.townBlock = townBlock; + this.player = player; + } + + @NotNull + public District getDistrict() { + return district; + } + + @NotNull + public TownBlock getTownBlock() { + return townBlock; + } + + @NotNull + public Player getPlayer() { + return player; + } + + @NotNull + 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/plot/district/DistrictCreatedEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictCreatedEvent.java new file mode 100644 index 0000000000..7c1889f34b --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictCreatedEvent.java @@ -0,0 +1,24 @@ +package com.palmergames.bukkit.towny.event.plot.district; + +import com.palmergames.bukkit.towny.object.District; +import com.palmergames.bukkit.towny.object.TownBlock; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a district is created. + */ +public class DistrictCreatedEvent extends DistrictAddEvent { + public DistrictCreatedEvent(District district, TownBlock townBlock, Player player) { + super(district, townBlock, player); + } + + /** + * @return The initial townblock that this district is being created with. + */ + @Override + @NotNull + public TownBlock getTownBlock() { + return super.getTownBlock(); + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictDeletedEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictDeletedEvent.java new file mode 100644 index 0000000000..a7b50a5a8a --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/plot/district/DistrictDeletedEvent.java @@ -0,0 +1,67 @@ +package com.palmergames.bukkit.towny.event.plot.district; + +import com.palmergames.bukkit.towny.event.CancellableTownyEvent; +import com.palmergames.bukkit.towny.object.District; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class DistrictDeletedEvent extends CancellableTownyEvent { + private static final HandlerList HANDLER_LIST = new HandlerList(); + private final District district; + private final Player player; + private final Cause deletionCause; + + public DistrictDeletedEvent(@NotNull District district, @Nullable Player player, @NotNull Cause deletionCause) { + this.district = district; + this.player = player; + this.deletionCause = deletionCause; + } + + /** + * @return The district that is being deleted. + */ + @NotNull + public District getDistrict() { + return district; + } + + /** + * @return The player associated with the deletion, if applicable. + */ + @Nullable + public Player getPlayer() { + return player; + } + + public Cause getDeletionCause() { + return deletionCause; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public enum Cause { + UNKNOWN, + /** + * The district was deleted by a player via the /plot district delete command. + */ + DELETED, + /** + * The district was deleted because all of its townblocks were removed. + */ + NO_TOWNBLOCKS, + /** + * The district was deleted because the town it was in being deleted/ruined. + */ + TOWN_DELETED, + } +} 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 b16401ede1..32b5ba4c21 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 @@ -90,6 +90,7 @@ public class Resident extends TownyObject implements InviteReceiver, EconomyHand private ScheduledTask respawnProtectionTask = null; private boolean respawnPickupWarningShown = false; // Prevents chat spam when a player attempts to pick up an item while under respawn protection. private String plotGroupName = null; + private String districtName = null; protected CachedTaxOwing cachedTaxOwing = null; public Resident(String name) { @@ -1062,6 +1063,18 @@ public void setPlotGroupName(String plotGroupName) { this.plotGroupName = plotGroupName; } + public boolean hasDistrictName() { + return districtName != null; + } + + public String getDistrictName() { + return districtName; + } + + public void setDistrictName(String districtName) { + this.districtName = districtName; + } + @ApiStatus.Internal @Override public boolean exists() { 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 56c1e6ff25..95dce01793 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 @@ -231,6 +231,13 @@ public enum PermissionNodes { TOWNY_COMMAND_PLOT_SET_SPLEEF("towny.command.plot.set.spleef"), TOWNY_COMMAND_PLOT_SET_INN("towny.command.plot.set.inn"), TOWNY_COMMAND_PLOT_SET_JAIL("towny.command.plot.set.jail"), + + TOWNY_COMMAND_PLOT_DISTRICT("towny.command.plot.district.*"), + TOWNY_COMMAND_PLOT_DISTRICT_ADD("towny.command.plot.district.add"), + TOWNY_COMMAND_PLOT_DISTRICT_REMOVE("towny.command.plot.district.remove"), + TOWNY_COMMAND_PLOT_DISTRICT_DELETE("towny.command.plot.district.delete"), + TOWNY_COMMAND_PLOT_DISTRICT_RENAME("towny.command.plot.district.rename"), + TOWNY_COMMAND_PLOT_GROUP("towny.command.plot.group.*"), TOWNY_COMMAND_PLOT_GROUP_ADD("towny.command.plot.group.add"), 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 b1a117e3f5..c5362e1901 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/util/NameValidation.java +++ b/Towny/src/main/java/com/palmergames/bukkit/util/NameValidation.java @@ -158,6 +158,18 @@ public static String checkAndFilterPlotGroupNameOrThrow(String name) throws Inva return checkAndFilterPlotNameOrThrow(filterCommas(name)); } + + /** + * Check and perform regex on District names + * + * @param name of a District object in {@link String} format. + * @return String of the valid name result. + * @throws InvalidNameException if the District name is invalid. + */ + public static String checkAndFilterDistrictNameOrThrow(String name) throws InvalidNameException { + return checkAndFilterPlotNameOrThrow(filterCommas(name)); + } + /** * Check and perform regex on Titles and Surnames given to residents. * diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index 99ea6ffb62..f86c9586d6 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -438,6 +438,11 @@ plot_help_8: "Opens the permission editor gui." plot_help_9: "Adds a resident as Trusted in the plot." plot_help_10: "Removes a resident as Trusted in the plot." +plot_district_help_0: "Ex: /plot district new ExpensivePlots" +plot_district_help_1: "Removes a plot from the specified district." +plot_district_help_2: "Deletes a district completely." +plot_district_help_3: "Renames the district you are standing in." + plot_group_help_0: "Ex: /plot group new ExpensivePlots" plot_group_help_1: "Removes a plot from the specified group." plot_group_help_2: "Deletes a plotgroup completely." @@ -2527,4 +2532,28 @@ msg_recieved_refund_for_deleted_object: "You have received the bank balance of y msg_err_you_already_own_this_plot: "You already own this plot." -msg_err_you_cannot_delete_this_nation: "You have been prevented from deleting this nation." \ No newline at end of file +msg_err_you_cannot_delete_this_nation: "You have been prevented from deleting this nation." + +status_district_name_and_size: 'Townblock is part of District: %s. District has %s Townblocks total.' + +msg_err_district_name_required: 'You must specify district name ie, /plot district add {name}' + +msg_district_renamed_from_x_to_y: 'District %s has been renamed to %s.' + +msg_err_plot_not_associated_with_a_district: 'This plot is not associated with a district.' + +msg_plot_was_removed_from_district_x: 'Plot &9(%s,%s) &2was removed from district %s.' + +msg_district_empty_deleted: 'The district %s had no townblocks and has been deleted.' + +msg_district_deleted: 'The district %s has been deleted.' + +msg_err_district_name_required: 'You must specify a district name ie, /plot district add {name}' + +msg_err_this_plot_is_already_part_of_the_district_x: 'This townblock is already part of the district: %s.' + +msg_townblock_transferred_from_x_to_x_district: 'Townblock transferred from %s to %s district.' + +msg_district_already_exists_did_you_want_to_transfer: 'This townblock is already a part of the %s district. Are you certain you want to transfer it to the %s district.' + +msg_plot_was_put_into_district_x: 'Plot &9(%s,%s) &2was put into district %s.' \ No newline at end of file From 08e4e4c03a275958aef02a47c8cb71bb3161c2c2 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 15:32:55 -0500 Subject: [PATCH 05/17] Fix yaml linter. --- Towny/src/main/resources/lang/en-US.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index f86c9586d6..9b781dd780 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -2548,8 +2548,6 @@ msg_district_empty_deleted: 'The district %s had no townblocks and has been dele msg_district_deleted: 'The district %s has been deleted.' -msg_err_district_name_required: 'You must specify a district name ie, /plot district add {name}' - msg_err_this_plot_is_already_part_of_the_district_x: 'This townblock is already part of the district: %s.' msg_townblock_transferred_from_x_to_x_district: 'Townblock transferred from %s to %s district.' From f89298038d5107981fa7c55927c752983fd356c7 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 16:13:37 -0500 Subject: [PATCH 06/17] Fix up commands a bit, add proximity tests to add/remove. --- .../bukkit/towny/command/PlotCommand.java | 39 ++++++++++++------- .../bukkit/towny/utils/ProximityUtil.java | 11 +++++- 2 files changed, 35 insertions(+), 15 deletions(-) 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 9c4ac8478f..3e341e7aa8 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 @@ -65,6 +65,7 @@ import com.palmergames.bukkit.towny.utils.NameUtil; import com.palmergames.bukkit.towny.utils.OutpostUtil; import com.palmergames.bukkit.towny.utils.PermissionGUIUtil; +import com.palmergames.bukkit.towny.utils.ProximityUtil; import com.palmergames.bukkit.util.BukkitTools; import com.palmergames.bukkit.util.ChatTools; import com.palmergames.bukkit.util.Colors; @@ -1297,7 +1298,7 @@ private void parseDistrict(String[] split, Resident resident, TownBlock townBloc case "rename" -> parseDistrictRename(split, townBlock, player); default -> { HelpMenu.PLOT_DISTRICT_HELP.send(player); - if (townBlock.hasPlotObjectGroup()) + if (townBlock.hasDistrict()) TownyMessaging.sendMsg(player, Translatable.of("status_district_name_and_size", townBlock.getDistrict().getName(), townBlock.getDistrict().getTownBlocks().size())); } } @@ -1309,7 +1310,7 @@ public void parseDistrictAdd(String[] split, TownBlock townBlock, Player player, Resident resident = getResidentOrThrow(player); if (split.length != 2 && !resident.hasDistrictName()) - throw new TownyException(Translatable.of("msg_err_plot_group_name_required")); + throw new TownyException(Translatable.of("msg_err_district_name_required")); String districtName = split.length == 2 ? NameValidation.checkAndFilterDistrictNameOrThrow(split[1]) @@ -1320,10 +1321,10 @@ public void parseDistrictAdd(String[] split, TownBlock townBlock, Player player, if (townBlock.hasDistrict()) { // Already has a District and it is the same name being used to re-add. if (townBlock.getDistrict().getName().equalsIgnoreCase(districtName)) - throw new TownyException(Translatable.of("msg_err_this_plot_is_already_part_of_the_plot_group_x", districtName)); + throw new TownyException(Translatable.of("msg_err_this_plot_is_already_part_of_the_district_x", districtName)); final String name = districtName; - // Already has a PlotGroup, ask if they want to transfer from one group to another. + // Already has a District, ask if they want to transfer from one district to another. Confirmation.runOnAccept( ()-> { District oldDistrict = townBlock.getDistrict(); oldDistrict.removeTownBlock(townBlock); @@ -1343,44 +1344,47 @@ public void parseDistrictAdd(String[] split, TownBlock townBlock, Player player, TownyMessaging.sendErrorMsg(player, e.getMessage(player)); } }) - .setTitle(Translatable.of("msg_plot_group_already_exists_did_you_want_to_transfer", townBlock.getPlotObjectGroup().getName(), split[1])) + .setTitle(Translatable.of("msg_district_already_exists_did_you_want_to_transfer", townBlock.getDistrict().getName(), split[1])) .sendTo(player); } else { - // Create a brand new plot group. + // Create a brand new district. createOrAddOnToDistrict(townBlock, town, player, districtName); resident.setDistrictName(districtName); - TownyMessaging.sendMsg(player, Translatable.of("msg_plot_was_put_into_group_x", townBlock.getX(), townBlock.getZ(), townBlock.getDistrict().getName())); + TownyMessaging.sendMsg(player, Translatable.of("msg_plot_was_put_into_district_x", townBlock.getX(), townBlock.getZ(), townBlock.getDistrict().getName())); } } private void createOrAddOnToDistrict(TownBlock townBlock, Town town, Player player, String districtName) throws TownyException { District newDistrict; - // Don't add the group to the town data if it's already there. + // Don't add the district to the town data if it's already there. if (town.hasDistrictName(districtName)) { newDistrict = town.getDistrictFromName(districtName); - + + ProximityUtil.testAdjacentClaimsRulesOrThrow(townBlock.getWorldCoord(), town, false, 1); + BukkitTools.ifCancelledThenThrow(new DistrictAddEvent(newDistrict, townBlock, player)); } else { - // This is a brand new PlotGroup, register it. + // This is a brand new District, register it. newDistrict = new District(UUID.randomUUID(), districtName, town); - + BukkitTools.ifCancelledThenThrow(new DistrictCreatedEvent(newDistrict, townBlock, player)); TownyUniverse.getInstance().registerDistrict(newDistrict); } - // Add group to townblock, this also adds the townblock to the group. + // Add district to townblock, this also adds the townblock to the district. townBlock.setDistrict(newDistrict); - // Add the plot group to the town set. + // Add the district to the town set. town.addDistrict(newDistrict); // Save changes. newDistrict.save(); townBlock.save(); } + public void parseDistrictDelete(TownBlock townBlock, Player player, Town town) throws TownyException { checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_PLOT_DISTRICT_DELETE.getNode()); @@ -1401,6 +1405,13 @@ public void parseDistrictRemove(TownBlock townBlock, Player player, Town town) t District district = catchMissingDistrict(townBlock); String name = district.getName(); + + try { + ProximityUtil.testAdjacentUnclaimsRulesOrThrow(townBlock.getWorldCoord(), town, 1); + } catch (TownyException e) { + throw new TownyException(Translatable.of("msg_err_cannot_remove_from_district_not_enough_adjacent_claims", name)); + } + // Remove the plot from the district. district.removeTownBlock(townBlock); @@ -1422,7 +1433,7 @@ public void parseDistrictRename(String[] split, TownBlock townBlock, Player play checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_PLOT_DISTRICT_RENAME.getNode()); if (split.length == 1) - throw new TownyException(Translatable.of("msg_err_plot_group_name_required")); + throw new TownyException(Translatable.of("msg_err_rename_district_name_required")); District district = catchMissingDistrict(townBlock); String newName= split[1]; diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java index 8717102f44..b8a926d6a1 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java @@ -96,6 +96,10 @@ public static void allowTownClaimOrThrow(TownyWorld world, WorldCoord townBlockT public static void testAdjacentClaimsRulesOrThrow(WorldCoord townBlockToClaim, Town town, boolean outpost) throws TownyException { int minAdjacentBlocks = TownySettings.getMinAdjacentBlocks(); + testAdjacentClaimsRulesOrThrow(townBlockToClaim, town, outpost, minAdjacentBlocks); + } + + public static void testAdjacentClaimsRulesOrThrow(WorldCoord townBlockToClaim, Town town, boolean outpost, int minAdjacentBlocks) throws TownyException { if (!outpost && minAdjacentBlocks > 0 && townHasClaimedEnoughLandToBeRestrictedByAdjacentClaims(town, minAdjacentBlocks)) { // Only consider the first worldCoord, larger selection-claims will automatically "bubble" anyways. int numAdjacent = numAdjacentTownOwnedTownBlocks(town, townBlockToClaim); @@ -162,6 +166,11 @@ public static void allowTownUnclaimOrThrow(TownyWorld world, WorldCoord townBloc public static void testAdjacentUnclaimsRulesOrThrow(WorldCoord townBlockToUnclaim, Town town) throws TownyException { // Prevent unclaiming land that would reduce the number of adjacent claims of neighbouring plots below the threshold. int minAdjacentBlocks = TownySettings.getMinAdjacentBlocks(); + testAdjacentUnclaimsRulesOrThrow(townBlockToUnclaim, town, minAdjacentBlocks); + } + + public static void testAdjacentUnclaimsRulesOrThrow(WorldCoord townBlockToUnclaim, Town town, int minAdjacentBlocks) throws TownyException { + // Prevent unclaiming land that would reduce the number of adjacent claims of neighbouring plots below the threshold. if (minAdjacentBlocks > 0 && townHasClaimedEnoughLandToBeRestrictedByAdjacentClaims(town, minAdjacentBlocks)) { WorldCoord firstWorldCoord = townBlockToUnclaim; for (WorldCoord wc : firstWorldCoord.getCardinallyAdjacentWorldCoords(true)) { @@ -170,7 +179,7 @@ public static void testAdjacentUnclaimsRulesOrThrow(WorldCoord townBlockToUnclai int numAdjacent = numAdjacentTownOwnedTownBlocks(town, wc); // The number of adjacement TBs is not enough and there is not a nearby outpost. if (numAdjacent - 1 < minAdjacentBlocks && numAdjacentOutposts(town, wc) == 0) - throw new TownyException(Translatable.of("msg_err_cannot_unclaim_not_enough_adjacent_claims", wc.getX(), wc.getZ(), numAdjacent)); + throw new TownyException(Translatable.of("msg_err_cannot_remove_from_district_not_enough_adjacent_claims", wc.getX(), wc.getZ(), numAdjacent)); } } } From 46a5cf157bac7aa2bbfc6abceb12938ce6853d8d Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 16:29:46 -0500 Subject: [PATCH 07/17] Add PlayerExitsFromDistrictEvent and PlayerEntersIntoDistrictEvent --- .../player/PlayerEntersIntoDistrictEvent.java | 68 +++++++++++++++++++ .../player/PlayerExitsFromDistrictEvent.java | 66 ++++++++++++++++++ .../towny/listeners/TownyPlayerListener.java | 62 ++++++++++++++++- 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerEntersIntoDistrictEvent.java create mode 100644 Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerExitsFromDistrictEvent.java diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerEntersIntoDistrictEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerEntersIntoDistrictEvent.java new file mode 100644 index 0000000000..201551b062 --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerEntersIntoDistrictEvent.java @@ -0,0 +1,68 @@ +package com.palmergames.bukkit.towny.event.player; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerMoveEvent; +import org.jetbrains.annotations.Nullable; + +import com.palmergames.bukkit.towny.TownyAPI; +import com.palmergames.bukkit.towny.object.District; +import com.palmergames.bukkit.towny.object.Resident; +import com.palmergames.bukkit.towny.object.WorldCoord; + +/** + * Thrown when a player crosses into Town border. + */ +public class PlayerEntersIntoDistrictEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private final District enteredDistrict; + private final PlayerMoveEvent pme; + private final WorldCoord from; + private final WorldCoord to; + private final Player player; + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public PlayerEntersIntoDistrictEvent(Player player, WorldCoord to, WorldCoord from, District enteredDistrict, PlayerMoveEvent pme) { + super(!Bukkit.getServer().isPrimaryThread()); + this.enteredDistrict = enteredDistrict; + this.player = player; + this.from = from; + this.pme = pme; + this.to = to; + } + + public Player getPlayer() { + return player; + } + + @Nullable + public Resident getResident() { + return TownyAPI.getInstance().getResident(player); + } + + public PlayerMoveEvent getPlayerMoveEvent() { + return pme; + } + + public District getEnteredDistrict() { + return enteredDistrict; + } + + public WorldCoord getFrom() { + return from; + } + + public WorldCoord getTo() { + return to; + } +} diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerExitsFromDistrictEvent.java b/Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerExitsFromDistrictEvent.java new file mode 100644 index 0000000000..0cb4cc3194 --- /dev/null +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/event/player/PlayerExitsFromDistrictEvent.java @@ -0,0 +1,66 @@ +package com.palmergames.bukkit.towny.event.player; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerMoveEvent; +import org.jetbrains.annotations.Nullable; + +import com.palmergames.bukkit.towny.TownyAPI; +import com.palmergames.bukkit.towny.object.District; +import com.palmergames.bukkit.towny.object.Resident; +import com.palmergames.bukkit.towny.object.WorldCoord; + +public class PlayerExitsFromDistrictEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private final District leftDistrict; + private final PlayerMoveEvent pme; + private final WorldCoord from; + private final Player player; + private final WorldCoord to; + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public PlayerExitsFromDistrictEvent(Player player, WorldCoord to, WorldCoord from, District leftDistrict, PlayerMoveEvent pme) { + super(!Bukkit.getServer().isPrimaryThread()); + this.leftDistrict = leftDistrict; + this.player = player; + this.from = from; + this.pme = pme; + this.to = to; + } + + public Player getPlayer() { + return player; + } + + @Nullable + public Resident getResident() { + return TownyAPI.getInstance().getResident(player); + } + + public PlayerMoveEvent getPlayerMoveEvent() { + return pme; + } + + public District getLeftDistrict() { + return leftDistrict; + } + + public WorldCoord getFrom() { + return from; + } + + public WorldCoord getTo() { + return to; + } +} 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 f14b19c1d6..fc51f51eb9 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 @@ -11,7 +11,9 @@ import com.palmergames.bukkit.towny.event.TitleNotificationEvent; import com.palmergames.bukkit.towny.event.executors.TownyActionEventExecutor; import com.palmergames.bukkit.towny.event.player.PlayerDeniedBedUseEvent; +import com.palmergames.bukkit.towny.event.player.PlayerEntersIntoDistrictEvent; import com.palmergames.bukkit.towny.event.player.PlayerEntersIntoTownBorderEvent; +import com.palmergames.bukkit.towny.event.player.PlayerExitsFromDistrictEvent; import com.palmergames.bukkit.towny.event.player.PlayerExitsFromTownBorderEvent; import com.palmergames.bukkit.towny.event.player.PlayerKeepsExperienceEvent; import com.palmergames.bukkit.towny.event.player.PlayerKeepsInventoryEvent; @@ -924,20 +926,78 @@ public void onPlayerChangePlotEvent(PlayerChangePlotEvent event) { if (to.isWilderness()) { // Gone from a Town into the wilderness. BukkitTools.fireEvent(new PlayerExitsFromTownBorderEvent(event.getPlayer(), to, from, from.getTownOrNull(), event.getMoveEvent())); + + if (from.getTownBlockOrNull().hasDistrict()) + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); } else if (from.isWilderness()) { // Gone from wilderness into Town. BukkitTools.fireEvent(new PlayerEntersIntoTownBorderEvent(event.getPlayer(), to, from, to.getTownOrNull(), event.getMoveEvent())); + if (to.getTownBlockOrNull().hasDistrict()) + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); // Both to and from have towns. } else if (to.getTownOrNull().equals(from.getTownOrNull())) { // The towns are the same, no event will fire. + if (from.getTownBlockOrNull().hasDistrict() && to.getTownBlockOrNull().hasDistrict()) { + if (!from.getTownBlockOrNull().getDistrict().getName().equals(to.getTownBlockOrNull().getDistrict().getName())) { + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + } + } else if (!from.getTownBlockOrNull().hasDistrict() && to.getTownBlockOrNull().hasDistrict()) { + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + } else if (from.getTownBlockOrNull().hasDistrict() && !to.getTownBlockOrNull().hasDistrict()) { + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + } + return; } else { // Player has left one Town and immediately entered a different one. BukkitTools.fireEvent(new PlayerEntersIntoTownBorderEvent(event.getPlayer(), to, from, to.getTownOrNull(), event.getMoveEvent())); BukkitTools.fireEvent(new PlayerExitsFromTownBorderEvent(event.getPlayer(), to, from, from.getTownOrNull(), event.getMoveEvent())); + + if (from.getTownBlockOrNull().hasDistrict()) + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + if (to.getTownBlockOrNull().hasDistrict()) + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); } } - + + /* + * PlayerChangePlotEvent that can fire the PlayerExitsFromDistrictEvent and PlayerEntersIntoDistrictEvent + */ + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerChangeDistricts(PlayerChangePlotEvent event) { + if (!TownyUniverse.getInstance().hasResident(event.getPlayer().getUniqueId())) + return; + WorldCoord from = event.getFrom(); + WorldCoord to = event.getTo(); + if (to.isWilderness() && from.isWilderness()) + // Both are wilderness, no event will fire. + return; + + if (to.isWilderness() && from.getTownBlockOrNull().hasDistrict()) { + // Gone from a Town into the wilderness. + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + + } else if (from.isWilderness() && to.getTownBlockOrNull().hasDistrict()) { + // Gone from wilderness into Town. + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + + } else if (to.getTownOrNull().equals(from.getTownOrNull()) + && from.getTownBlockOrNull().hasDistrict() && to.getTownBlockOrNull().hasDistrict() + && !from.getTownBlockOrNull().getDistrict().getName().equals(to.getTownBlockOrNull().getDistrict().getName())) { + // Moving in same town, between two different Districts. + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + + } else { + // Player has left one Town and immediately entered a different one, check if there were districts. + if (from.getTownBlockOrNull().hasDistrict()) + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + if (to.getTownBlockOrNull().hasDistrict()) + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + } + } + /* * onOutlawEnterTown * - Handles outlaws entering a town they are outlawed in. From 019fdd6fa1848bc5716dc85ebb17eb6abc255c02 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 16:30:04 -0500 Subject: [PATCH 08/17] Lang file --- Towny/src/main/resources/lang/en-US.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index 9b781dd780..5e75e58795 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -2538,6 +2538,8 @@ status_district_name_and_size: 'Townblock is part of District: %s. District has msg_err_district_name_required: 'You must specify district name ie, /plot district add {name}' +msg_err_rename_district_name_required: 'You must specify district name ie, /plot district rename {name}' + msg_district_renamed_from_x_to_y: 'District %s has been renamed to %s.' msg_err_plot_not_associated_with_a_district: 'This plot is not associated with a district.' @@ -2554,4 +2556,6 @@ msg_townblock_transferred_from_x_to_x_district: 'Townblock transferred from %s t msg_district_already_exists_did_you_want_to_transfer: 'This townblock is already a part of the %s district. Are you certain you want to transfer it to the %s district.' -msg_plot_was_put_into_district_x: 'Plot &9(%s,%s) &2was put into district %s.' \ No newline at end of file +msg_plot_was_put_into_district_x: 'Plot &9(%s,%s) &2was put into district %s.' + +msg_err_cannot_remove_from_district_not_enough_adjacent_claims: "You cannot remove this townblock from the district %s, because the a part of the District would be detached from the rest." \ No newline at end of file From 64c97dba44fee3d0438e57b1adcffeb44d1ec128 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 16:45:18 -0500 Subject: [PATCH 09/17] Add District to ChunkNotification --- .../bukkit/config/ConfigNodes.java | 1 + .../bukkit/towny/ChunkNotification.java | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java b/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java index 00b03533d2..1de66bbf07 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java +++ b/Towny/src/main/java/com/palmergames/bukkit/config/ConfigNodes.java @@ -2020,6 +2020,7 @@ public enum ConfigNodes { NOTIFICATION_PLOT_NOTFORSALE("notification.plot.notforsale", "&e[Not For Sale]"), NOTIFICATION_PLOT_TYPE("notification.plot.type", "&6[%s]"), NOTIFICATION_GROUP("notification.group", "&f[%s]"), + NOTIFICATION_DISTRICT("notification.district", "&2[%s]"), NOTIFICATION_TOWN_NAMES_ARE_VERBOSE( "notification.town_names_are_verbose", "true", diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/ChunkNotification.java b/Towny/src/main/java/com/palmergames/bukkit/towny/ChunkNotification.java index bdaf4e9c15..a73a2e2feb 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/ChunkNotification.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/ChunkNotification.java @@ -14,6 +14,7 @@ import com.palmergames.bukkit.towny.object.TownBlockType; import com.palmergames.bukkit.towny.object.TownyWorld; import com.palmergames.bukkit.towny.object.WorldCoord; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.PlayerCache.TownBlockStatus; import com.palmergames.bukkit.towny.utils.CombatUtil; import com.palmergames.bukkit.towny.utils.PlayerCacheUtil; @@ -41,6 +42,7 @@ public class ChunkNotification { public static String notForSaleNotificationFormat = Colors.Yellow + "[Not For Sale]"; public static String plotTypeNotificationFormat = Colors.Gold + "[%s]"; public static String groupNotificationFormat = Colors.White + "[%s]"; + public static String districtNotificationFormat = Colors.DARK_GREEN + "[%s]"; /** * Called on Config load. @@ -64,16 +66,18 @@ public static void loadFormatStrings() { notForSaleNotificationFormat = Colors.translateColorCodes(TownySettings.getString(ConfigNodes.NOTIFICATION_PLOT_NOTFORSALE)); plotTypeNotificationFormat = Colors.translateColorCodes(TownySettings.getString(ConfigNodes.NOTIFICATION_PLOT_TYPE)); groupNotificationFormat = Colors.translateColorCodes(TownySettings.getString(ConfigNodes.NOTIFICATION_GROUP)); + districtNotificationFormat = Colors.translateColorCodes(TownySettings.getString(ConfigNodes.NOTIFICATION_DISTRICT)); } WorldCoord from, to; boolean fromWild = false, toWild = false, toForSale = false, fromForSale = false, - toHomeBlock = false, toOutpostBlock = false, toPlotGroupBlock = false; + toHomeBlock = false, toOutpostBlock = false, toPlotGroupBlock = false, toDistrictBlock = false; TownBlock fromTownBlock, toTownBlock = null; Town fromTown = null, toTown = null; Resident fromResident = null, toResident = null; TownBlockType fromPlotType = null, toPlotType = null; PlotGroup fromPlotGroup = null, toPlotGroup = null; + District fromDistrict = null, toDistrict = null; public ChunkNotification(WorldCoord from, WorldCoord to) { @@ -88,6 +92,9 @@ public ChunkNotification(WorldCoord from, WorldCoord to) { fromPlotGroup = fromTownBlock.getPlotObjectGroup(); fromForSale = fromPlotGroup.getPrice() != -1; } + if (fromTownBlock.hasDistrict()) { + fromDistrict = fromTownBlock.getDistrict(); + } fromTown = fromTownBlock.getTownOrNull(); fromResident = fromTownBlock.getResidentOrNull(); @@ -109,7 +116,10 @@ public ChunkNotification(WorldCoord from, WorldCoord to) { toPlotGroup = toTownBlock.getPlotObjectGroup(); toForSale = toPlotGroup.getPrice() != -1; } - + toDistrictBlock = toTownBlock.hasDistrict(); + if (toDistrictBlock) { + toDistrict = toTownBlock.getDistrict(); + } } else { toWild = true; } @@ -277,6 +287,10 @@ public List getPlotNotificationContent() { if (output != null && output.length() > 0) out.add(output); + output = getDistrictNotification(); + if (output != null && output.length() > 0) + out.add(output); + return out; } @@ -323,6 +337,12 @@ public String getGroupNotification() { return null; } + public String getDistrictNotification() { + if (toDistrictBlock && (fromDistrict != toDistrict)) + return String.format(districtNotificationFormat, StringMgmt.remUnderscore(toDistrict.getName())); + return null; + } + public String getPlotTypeNotification() { if (toPlotType != null && !toPlotType.equals(fromPlotType) && !TownBlockType.RESIDENTIAL.equals(toPlotType)) From 7c21522c42ac5e2f9420eb3d3aaa461f34bc1569 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Thu, 20 Jun 2024 19:29:30 -0500 Subject: [PATCH 10/17] Handle permissions in plugin.yml, make the command limited to players who are also mayors or equivalent. --- .../palmergames/bukkit/towny/command/PlotCommand.java | 11 ++++++++--- Towny/src/main/resources/plugin.yml | 10 ++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) 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 3e341e7aa8..43c93c0427 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 @@ -1279,10 +1279,15 @@ private void parseDistrict(String[] split, Resident resident, TownBlock townBloc Town town = townBlock.getTownOrNull(); if (town == null) throw new TownyException(Translatable.of("msg_not_claimed_1")); + + if (!town.hasResident(player)) + throw new TownyException(Translatable.of("msg_err_not_part_town")); - // Test we are allowed to work on this plot - // If this fails it will trigger a TownyException. - TownyAPI.getInstance().testPlotOwnerOrThrow(resident, townBlock); + try { + checkPermOrThrow(player, PermissionNodes.TOWNY_COMMAND_PLOT_ASMAYOR.getNode()); + } catch (NoPermissionException e) { + throw new TownyException(Translatable.of("msg_not_mayor_ass")); + } if (split.length <= 0 || split[0].equalsIgnoreCase("?")) { HelpMenu.PLOT_DISTRICT_HELP.send(player); diff --git a/Towny/src/main/resources/plugin.yml b/Towny/src/main/resources/plugin.yml index f19f1331b4..e4d79bc31e 100644 --- a/Towny/src/main/resources/plugin.yml +++ b/Towny/src/main/resources/plugin.yml @@ -564,6 +564,7 @@ permissions: towny.command.plot.set.*: true towny.command.plot.unclaim: true towny.command.plot.group.*: true + towny.command.plot.district.*: true towny.command.plot.trust: true towny.command.plot.toggle.*: @@ -607,6 +608,15 @@ permissions: towny.command.plot.perm.add: true towny.command.plot.perm.remove: true + towny.command.plot.district.*: + description: User can access the plot district command. + default: false + children: + towny.command.plot.district.add: true + towny.command.plot.district.remove: true + towny.command.plot.district.delete: true + towny.command.plot.district.rename: true + towny.command.plot.group.*: description: User can access the plot group command. default: false From 2a3cd01e615e76e9b1a042b463f35e5df0cf58f4 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Fri, 21 Jun 2024 08:57:13 -0500 Subject: [PATCH 11/17] Make new Proximity methods that will actually work... Block unclaiming which would mangle Districts. --- .../bukkit/towny/command/PlotCommand.java | 4 +- .../towny/listeners/TownyCustomListener.java | 22 +++++++++++ .../bukkit/towny/utils/ProximityUtil.java | 37 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) 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 43c93c0427..fde055affe 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 @@ -1366,7 +1366,7 @@ private void createOrAddOnToDistrict(TownBlock townBlock, Town town, Player play if (town.hasDistrictName(districtName)) { newDistrict = town.getDistrictFromName(districtName); - ProximityUtil.testAdjacentClaimsRulesOrThrow(townBlock.getWorldCoord(), town, false, 1); + ProximityUtil.testAdjacentAddDistrictRulesOrThrow(townBlock.getWorldCoord(), town, newDistrict, 0); BukkitTools.ifCancelledThenThrow(new DistrictAddEvent(newDistrict, townBlock, player)); @@ -1412,7 +1412,7 @@ public void parseDistrictRemove(TownBlock townBlock, Player player, Town town) t String name = district.getName(); try { - ProximityUtil.testAdjacentUnclaimsRulesOrThrow(townBlock.getWorldCoord(), town, 1); + ProximityUtil.testAdjacentRemoveDistrictRulesOrThrow(townBlock.getWorldCoord(), town, district, 1); } catch (TownyException e) { throw new TownyException(Translatable.of("msg_err_cannot_remove_from_district_not_enough_adjacent_claims", name)); } 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 efc2494a42..dbe9fded42 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 @@ -22,12 +22,14 @@ import com.palmergames.bukkit.towny.event.damage.TownyPlayerDamagePlayerEvent; import com.palmergames.bukkit.towny.event.nation.NationPreTownLeaveEvent; 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.PlayerCache; import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.SpawnType; import com.palmergames.bukkit.towny.object.Town; +import com.palmergames.bukkit.towny.object.TownBlock; import com.palmergames.bukkit.towny.object.TownyWorld; import com.palmergames.bukkit.towny.object.Translatable; import com.palmergames.bukkit.towny.object.Translation; @@ -35,6 +37,7 @@ 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.Colors; import com.palmergames.bukkit.util.DrawSmokeTaskFactory; @@ -213,6 +216,25 @@ public void onTownUnclaim(TownPreUnclaimCmdEvent event) { } } + /** + * Used to prevent unclaiming when a District would be cut in two parts. + * + * @param event {@link TownPreUnclaimEvent} thrown when someone runs /t unclaim. + */ + @EventHandler(ignoreCancelled = true) + public void onTownUnclaimDistrict(TownPreUnclaimEvent event) { + TownBlock townBlock = event.getTownBlock(); + if (!townBlock.hasDistrict()) + return; + + try { + ProximityUtil.testAdjacentRemoveDistrictRulesOrThrow(townBlock.getWorldCoord(), event.getTown(), townBlock.getDistrict(), 1); + } catch (TownyException e) { + event.setCancelled(true); + event.setCancelMessage(e.getMessage()); + } + } + /** * Used to warn towns when they're approaching their claim limit, when the * takeoverclaim feature is enabled, as well as claiming particles. diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java index b8a926d6a1..382d48eae4 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java @@ -11,6 +11,7 @@ import com.palmergames.bukkit.towny.TownySettings; import com.palmergames.bukkit.towny.exceptions.TownyException; import com.palmergames.bukkit.towny.object.Coord; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.Nation; import com.palmergames.bukkit.towny.object.Town; import com.palmergames.bukkit.towny.object.TownBlock; @@ -179,11 +180,47 @@ public static void testAdjacentUnclaimsRulesOrThrow(WorldCoord townBlockToUnclai int numAdjacent = numAdjacentTownOwnedTownBlocks(town, wc); // The number of adjacement TBs is not enough and there is not a nearby outpost. if (numAdjacent - 1 < minAdjacentBlocks && numAdjacentOutposts(town, wc) == 0) + throw new TownyException(Translatable.of("msg_err_cannot_unclaim_not_enough_adjacent_claims", wc.getX(), wc.getZ(), numAdjacent)); + } + } + } + + /* + * District add/remove methods + */ + + public static void testAdjacentAddDistrictRulesOrThrow(WorldCoord townBlockToClaim, Town town, District district, int minAdjacentBlocks) throws TownyException { + if (minAdjacentBlocks > 0 && townHasClaimedEnoughLandToBeRestrictedByAdjacentClaims(town, minAdjacentBlocks)) { + int numAdjacent = numAdjacentDistrictTownBlocks(town, district, townBlockToClaim); + // The number of adjacement TBs with the same District is not enough. + if (numAdjacent < minAdjacentBlocks) + throw new TownyException(Translatable.of("msg_min_adjacent_blocks", minAdjacentBlocks, numAdjacent)); + } + } + + public static void testAdjacentRemoveDistrictRulesOrThrow(WorldCoord townBlockToUnclaim, Town town, District district, int minAdjacentBlocks) throws TownyException { + // Prevent removing parts of Districts that would cause a district to split into two sections. + if (minAdjacentBlocks > 0 && townHasClaimedEnoughLandToBeRestrictedByAdjacentClaims(town, minAdjacentBlocks)) { + WorldCoord firstWorldCoord = townBlockToUnclaim; + for (WorldCoord wc : firstWorldCoord.getCardinallyAdjacentWorldCoords(true)) { + if (wc.isWilderness() || !wc.hasTown(town) || !wc.getTownBlock().hasDistrict() || !wc.getTownBlock().getDistrict().getName().equals(district.getName())) + continue; + int numAdjacent = numAdjacentDistrictTownBlocks(town, district, wc); + // The number of adjacement TBs with the same District is not enough + if (numAdjacent - 1 < minAdjacentBlocks) throw new TownyException(Translatable.of("msg_err_cannot_remove_from_district_not_enough_adjacent_claims", wc.getX(), wc.getZ(), numAdjacent)); } } } + private static int numAdjacentDistrictTownBlocks(Town town, District district, WorldCoord worldCoord) { + return (int) worldCoord.getCardinallyAdjacentWorldCoords(true).stream() + .filter(wc -> wc.hasTown(town) && wc.getTownBlockOrNull() != null) + .map(wc -> wc.getTownBlockOrNull()) + .filter(tb -> tb.hasDistrict() && tb.getDistrict().getName().equals(district.getName())) + .count(); + } + /* * Nation Promixity Methods */ From 1d2415116caf88700bfd7e8a9d3e226a09517b72 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Fri, 21 Jun 2024 12:01:16 -0500 Subject: [PATCH 12/17] Add equals method, do a bit of cleanup, add a missed proximity test. --- .../bukkit/towny/command/PlotCommand.java | 7 ++++-- .../towny/listeners/TownyPlayerListener.java | 25 ++----------------- .../bukkit/towny/object/District.java | 21 ++++++++++++++++ .../bukkit/towny/utils/ProximityUtil.java | 2 +- 4 files changed, 29 insertions(+), 26 deletions(-) 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 fde055affe..5f2b535d55 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 @@ -1328,10 +1328,13 @@ public void parseDistrictAdd(String[] split, TownBlock townBlock, Player player, if (townBlock.getDistrict().getName().equalsIgnoreCase(districtName)) throw new TownyException(Translatable.of("msg_err_this_plot_is_already_part_of_the_district_x", districtName)); + District oldDistrict = townBlock.getDistrict(); + ProximityUtil.testAdjacentRemoveDistrictRulesOrThrow(townBlock.getWorldCoord(), town, oldDistrict, 1); + final String name = districtName; // Already has a District, ask if they want to transfer from one district to another. Confirmation.runOnAccept( ()-> { - District oldDistrict = townBlock.getDistrict(); + oldDistrict.removeTownBlock(townBlock); if (oldDistrict.getTownBlocks().isEmpty() && !BukkitTools.isEventCancelled(new DistrictDeletedEvent(oldDistrict, player, DistrictDeletedEvent.Cause.NO_TOWNBLOCKS))) { String oldName = oldDistrict.getName(); @@ -1366,7 +1369,7 @@ private void createOrAddOnToDistrict(TownBlock townBlock, Town town, Player play if (town.hasDistrictName(districtName)) { newDistrict = town.getDistrictFromName(districtName); - ProximityUtil.testAdjacentAddDistrictRulesOrThrow(townBlock.getWorldCoord(), town, newDistrict, 0); + ProximityUtil.testAdjacentAddDistrictRulesOrThrow(townBlock.getWorldCoord(), town, newDistrict, 1); BukkitTools.ifCancelledThenThrow(new DistrictAddEvent(newDistrict, townBlock, player)); 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 fc51f51eb9..ccf265781b 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 @@ -926,41 +926,20 @@ public void onPlayerChangePlotEvent(PlayerChangePlotEvent event) { if (to.isWilderness()) { // Gone from a Town into the wilderness. BukkitTools.fireEvent(new PlayerExitsFromTownBorderEvent(event.getPlayer(), to, from, from.getTownOrNull(), event.getMoveEvent())); - - if (from.getTownBlockOrNull().hasDistrict()) - BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); } else if (from.isWilderness()) { // Gone from wilderness into Town. BukkitTools.fireEvent(new PlayerEntersIntoTownBorderEvent(event.getPlayer(), to, from, to.getTownOrNull(), event.getMoveEvent())); - if (to.getTownBlockOrNull().hasDistrict()) - BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); // Both to and from have towns. } else if (to.getTownOrNull().equals(from.getTownOrNull())) { // The towns are the same, no event will fire. - if (from.getTownBlockOrNull().hasDistrict() && to.getTownBlockOrNull().hasDistrict()) { - if (!from.getTownBlockOrNull().getDistrict().getName().equals(to.getTownBlockOrNull().getDistrict().getName())) { - BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); - BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); - } - } else if (!from.getTownBlockOrNull().hasDistrict() && to.getTownBlockOrNull().hasDistrict()) { - BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); - } else if (from.getTownBlockOrNull().hasDistrict() && !to.getTownBlockOrNull().hasDistrict()) { - BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); - } - return; } else { // Player has left one Town and immediately entered a different one. BukkitTools.fireEvent(new PlayerEntersIntoTownBorderEvent(event.getPlayer(), to, from, to.getTownOrNull(), event.getMoveEvent())); BukkitTools.fireEvent(new PlayerExitsFromTownBorderEvent(event.getPlayer(), to, from, from.getTownOrNull(), event.getMoveEvent())); - - if (from.getTownBlockOrNull().hasDistrict()) - BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); - if (to.getTownBlockOrNull().hasDistrict()) - BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); } } - + /* * PlayerChangePlotEvent that can fire the PlayerExitsFromDistrictEvent and PlayerEntersIntoDistrictEvent */ @@ -984,7 +963,7 @@ public void onPlayerChangeDistricts(PlayerChangePlotEvent event) { } else if (to.getTownOrNull().equals(from.getTownOrNull()) && from.getTownBlockOrNull().hasDistrict() && to.getTownBlockOrNull().hasDistrict() - && !from.getTownBlockOrNull().getDistrict().getName().equals(to.getTownBlockOrNull().getDistrict().getName())) { + && !from.getTownBlockOrNull().getDistrict().equals(to.getTownBlockOrNull().getDistrict())) { // Moving in same town, between two different Districts. BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java index 15c2e9fd10..c0b4405ad6 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/District.java @@ -6,6 +6,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.UUID; /** @@ -65,6 +66,26 @@ public void setTown(Town town) { } } + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Objects.hash(town, townBlocks, getName()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + District other = (District) obj; + return Objects.equals(town, other.town) && Objects.equals(getName(), other.getName()); + } + public Town getTown() { return town; } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java index 382d48eae4..b1c8f4d280 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/ProximityUtil.java @@ -217,7 +217,7 @@ private static int numAdjacentDistrictTownBlocks(Town town, District district, W return (int) worldCoord.getCardinallyAdjacentWorldCoords(true).stream() .filter(wc -> wc.hasTown(town) && wc.getTownBlockOrNull() != null) .map(wc -> wc.getTownBlockOrNull()) - .filter(tb -> tb.hasDistrict() && tb.getDistrict().getName().equals(district.getName())) + .filter(tb -> tb.hasDistrict() && tb.getDistrict().equals(district)) .count(); } From 01313e95c4da5d0d7b232f0625df96391d733d15 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Fri, 21 Jun 2024 12:03:59 -0500 Subject: [PATCH 13/17] Update colours used in lang strings. --- Towny/src/main/resources/lang/en-US.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index 5e75e58795..655ae7b802 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -2544,7 +2544,7 @@ msg_district_renamed_from_x_to_y: 'District %s has been renamed to %s.' msg_err_plot_not_associated_with_a_district: 'This plot is not associated with a district.' -msg_plot_was_removed_from_district_x: 'Plot &9(%s,%s) &2was removed from district %s.' +msg_plot_was_removed_from_district_x: 'Plot (%s,%s) was removed from district %s.' msg_district_empty_deleted: 'The district %s had no townblocks and has been deleted.' @@ -2556,6 +2556,6 @@ msg_townblock_transferred_from_x_to_x_district: 'Townblock transferred from %s t msg_district_already_exists_did_you_want_to_transfer: 'This townblock is already a part of the %s district. Are you certain you want to transfer it to the %s district.' -msg_plot_was_put_into_district_x: 'Plot &9(%s,%s) &2was put into district %s.' +msg_plot_was_put_into_district_x: 'Plot (%s,%s) was put into district %s.' msg_err_cannot_remove_from_district_not_enough_adjacent_claims: "You cannot remove this townblock from the district %s, because the a part of the District would be detached from the rest." \ No newline at end of file From 0d94ab79f3aa886ebeedba3e4ba9ee78ddf5538b Mon Sep 17 00:00:00 2001 From: LlmDl Date: Fri, 21 Jun 2024 12:32:12 -0500 Subject: [PATCH 14/17] Fixes found during testing. --- .../bukkit/towny/db/TownyFlatFileSource.java | 2 ++ .../towny/listeners/TownyPlayerListener.java | 36 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) 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 4fd521c58b..866460f90a 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 @@ -84,6 +84,8 @@ public TownyFlatFileSource(Towny plugin, TownyUniverse universe) { dataFolderPath + File.separator + "townblocks", dataFolderPath + File.separator + "plotgroups", dataFolderPath + File.separator + "plotgroups" + File.separator + "deleted", + dataFolderPath + File.separator + "districts", + dataFolderPath + File.separator + "districts" + File.separator + "deleted", dataFolderPath + File.separator + "jails", dataFolderPath + File.separator + "jails" + File.separator + "deleted" )) { 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 ccf265781b..dedbbc6a13 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 @@ -20,6 +20,7 @@ import com.palmergames.bukkit.towny.event.teleport.CancelledTownyTeleportEvent.CancelledTeleportReason; import com.palmergames.bukkit.towny.hooks.PluginIntegrations; import com.palmergames.bukkit.towny.object.CommandList; +import com.palmergames.bukkit.towny.object.District; import com.palmergames.bukkit.towny.object.PlayerCache; import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.Town; @@ -947,33 +948,38 @@ public void onPlayerChangePlotEvent(PlayerChangePlotEvent event) { public void onPlayerChangeDistricts(PlayerChangePlotEvent event) { if (!TownyUniverse.getInstance().hasResident(event.getPlayer().getUniqueId())) return; + WorldCoord from = event.getFrom(); WorldCoord to = event.getTo(); - if (to.isWilderness() && from.isWilderness()) - // Both are wilderness, no event will fire. + boolean fromHasDistrict = !from.isWilderness() && from.getTownBlockOrNull().hasDistrict(); + boolean toHasDistrict = !to.isWilderness() && to.getTownBlockOrNull().hasDistrict(); + if (to.isWilderness() && from.isWilderness() || (!fromHasDistrict && !toHasDistrict)) + // Both are wilderness, or neither plot involves a District. No event will fire. return; - if (to.isWilderness() && from.getTownBlockOrNull().hasDistrict()) { + District fromDistrict = fromHasDistrict ? from.getTownBlockOrNull().getDistrict() : null; + District toDistrict = toHasDistrict ? to.getTownBlockOrNull().getDistrict() : null; + + if (to.isWilderness() && fromHasDistrict) { // Gone from a Town into the wilderness. - BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, fromDistrict, event.getMoveEvent())); - } else if (from.isWilderness() && to.getTownBlockOrNull().hasDistrict()) { + } else if (from.isWilderness() && toHasDistrict) { // Gone from wilderness into Town. - BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, toDistrict, event.getMoveEvent())); - } else if (to.getTownOrNull().equals(from.getTownOrNull()) - && from.getTownBlockOrNull().hasDistrict() && to.getTownBlockOrNull().hasDistrict() - && !from.getTownBlockOrNull().getDistrict().equals(to.getTownBlockOrNull().getDistrict())) { + } else if (!to.isWilderness() && !from.isWilderness() && to.getTownOrNull().equals(from.getTownOrNull()) + && fromHasDistrict && toHasDistrict && !fromDistrict.equals(toDistrict)) { // Moving in same town, between two different Districts. - BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); - BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, fromDistrict, event.getMoveEvent())); + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, toDistrict, event.getMoveEvent())); } else { // Player has left one Town and immediately entered a different one, check if there were districts. - if (from.getTownBlockOrNull().hasDistrict()) - BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, from.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); - if (to.getTownBlockOrNull().hasDistrict()) - BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, to.getTownBlockOrNull().getDistrict(), event.getMoveEvent())); + if (fromHasDistrict) + BukkitTools.fireEvent(new PlayerExitsFromDistrictEvent(event.getPlayer(), to, from, fromDistrict, event.getMoveEvent())); + if (toHasDistrict) + BukkitTools.fireEvent(new PlayerEntersIntoDistrictEvent(event.getPlayer(), to, from, toDistrict, event.getMoveEvent())); } } From 6d338d70ae72f7fd7a89aa2192541b436781cd7d Mon Sep 17 00:00:00 2001 From: LlmDl Date: Fri, 21 Jun 2024 15:03:24 -0500 Subject: [PATCH 15/17] Display districs on the townblock status screen as well as in the ascii map hovers. --- .../java/com/palmergames/bukkit/towny/TownyAsciiMap.java | 9 +++++++++ .../com/palmergames/bukkit/towny/TownyFormatter.java | 3 +++ Towny/src/main/resources/lang/en-US.yml | 6 +++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyAsciiMap.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyAsciiMap.java index 9dd36fc7f3..72646e86cb 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyAsciiMap.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyAsciiMap.java @@ -170,6 +170,7 @@ else if (townblock.isOutpost()) Component forSaleComponent = Component.empty(); Component claimedAtComponent = Component.empty(); Component groupComponent = Component.empty(); + Component districtComponent = Component.empty(); if (TownyEconomyHandler.isActive()) { double cost = townblock.hasPlotObjectGroup() @@ -192,6 +193,13 @@ else if (townblock.isOutpost()) .append(translator.component("map_hover_plots", townblock.getPlotObjectGroup().getTownBlocks().size()).color(NamedTextColor.GREEN) .append(Component.newline())))); + if (townblock.hasDistrict()) + districtComponent = translator.component("map_hover_district").color(NamedTextColor.DARK_GREEN) + .append(Component.text(townblock.getDistrict().getFormattedName(), NamedTextColor.GREEN) + .append(translator.component("map_hover_plot_group_size").color(NamedTextColor.DARK_GREEN) + .append(translator.component("map_hover_plots", townblock.getDistrict().getTownBlocks().size()).color(NamedTextColor.GREEN) + .append(Component.newline())))); + if (townblock.hasResident()) residentComponent = Component.text(" (" + townblock.getResidentOrNull().getName() + ")", NamedTextColor.GREEN); @@ -209,6 +217,7 @@ else if (townblock.isOutpost()) Component hoverComponent = townComponent .append(plotTypeComponent) + .append(districtComponent) .append(groupComponent) .append(forSaleComponent) .append(claimedAtComponent) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyFormatter.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyFormatter.java index 476cad27b1..68d004cdb9 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyFormatter.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyFormatter.java @@ -110,6 +110,9 @@ public static StatusScreen getStatus(TownBlock townBlock, Player player) { screen.addComponentOf("firespread", colourKeyValue(translator.of("firespread"), ((world.isForceFire() || townBlock.getPermissions().fire) ? translator.of("status_on"):translator.of("status_off")))); screen.addComponentOf("mobspawns", colourKeyValue(translator.of("mobspawns"), ((world.isForceTownMobs() || townBlock.getPermissions().mobs || town.isAdminEnabledMobs()) ? translator.of("status_on"): translator.of("status_off")))); + if (townBlock.hasDistrict()) + screen.addComponentOf("district", colourKey(translator.of("status_district_name_and_size", townBlock.getDistrict().getName(), townBlock.getDistrict().getTownBlocks().size()))); + if (townBlock.hasPlotObjectGroup()) screen.addComponentOf("plotgroup", colourKey(translator.of("status_plot_group_name_and_size", townBlock.getPlotObjectGroup().getName(), townBlock.getPlotObjectGroup().getTownBlocks().size()))); diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index 655ae7b802..14d4d8f912 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -2558,4 +2558,8 @@ msg_district_already_exists_did_you_want_to_transfer: 'This townblock is already msg_plot_was_put_into_district_x: 'Plot (%s,%s) was put into district %s.' -msg_err_cannot_remove_from_district_not_enough_adjacent_claims: "You cannot remove this townblock from the district %s, because the a part of the District would be detached from the rest." \ No newline at end of file +msg_err_cannot_remove_from_district_not_enough_adjacent_claims: "You cannot remove this townblock from the district %s, because the a part of the District would be detached from the rest." + +map_hover_district: 'District: ' + +status_district_name_and_size: 'Townblock is part of District: %s. District has %s Townblocks total.' \ No newline at end of file From e19af9776cc1fbedbdd490bc8199f97f3929f1cd Mon Sep 17 00:00:00 2001 From: LlmDl Date: Fri, 21 Jun 2024 15:05:21 -0500 Subject: [PATCH 16/17] Fix YAML linter complaint. --- Towny/src/main/resources/lang/en-US.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Towny/src/main/resources/lang/en-US.yml b/Towny/src/main/resources/lang/en-US.yml index 14d4d8f912..b2529e7d49 100644 --- a/Towny/src/main/resources/lang/en-US.yml +++ b/Towny/src/main/resources/lang/en-US.yml @@ -2560,6 +2560,4 @@ msg_plot_was_put_into_district_x: 'Plot (%s,%s) was put into d msg_err_cannot_remove_from_district_not_enough_adjacent_claims: "You cannot remove this townblock from the district %s, because the a part of the District would be detached from the rest." -map_hover_district: 'District: ' - -status_district_name_and_size: 'Townblock is part of District: %s. District has %s Townblocks total.' \ No newline at end of file +map_hover_district: 'District: ' \ No newline at end of file From 9cc1ad975d08913ad007e328bafb41b0e5cd811c Mon Sep 17 00:00:00 2001 From: LlmDl Date: Fri, 21 Jun 2024 15:17:32 -0500 Subject: [PATCH 17/17] Make the perm hud aware of Districts. --- .../com/palmergames/bukkit/towny/huds/PermHUD.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/huds/PermHUD.java b/Towny/src/main/java/com/palmergames/bukkit/towny/huds/PermHUD.java index bebc609724..e1e76e729a 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/huds/PermHUD.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/huds/PermHUD.java @@ -38,6 +38,7 @@ public class PermHUD { /* Scoreboards use Teams here is our team names.*/ private static final String HUD_OBJECTIVE = "PERM_HUD_OBJ"; private static final String TEAM_PERMS_TITLE = "permsTitle"; + private static final String TEAM_DISTRICT_NAME = "districtName"; private static final String TEAM_PLOT_NAME = "plot_name"; private static final String TEAM_PLOT_COST = "plot_cost"; private static final String TEAM_BUILD = "build"; @@ -64,7 +65,7 @@ public static String permHudTestKey() { public static void updatePerms(Player p, WorldCoord worldCoord) { Translator translator = Translator.locale(p); - String plotName, build, destroy, switching, item, type, pvp, explosions, firespread, mobspawn, title; + String districtName, plotName, build, destroy, switching, item, type, pvp, explosions, firespread, mobspawn, title; Scoreboard board = p.getScoreboard(); // Due to tick delay (probably not confirmed), a HUD can actually be removed from the player. // Causing board to return null, and since we don't create a new board, a NullPointerException occurs. @@ -92,6 +93,9 @@ public static void updatePerms(Player p, WorldCoord worldCoord) { // Displays the name of the owner, and if the owner is a resident the town name as well. title = GOLD + owner.getName() + (townBlock.hasResident() ? " (" + townBlock.getTownOrNull().getName() + ")" : ""); + // District name + districtName = townBlock.hasDistrict() ? townBlock.getDistrict().getFormattedName() : ""; + // Plot Type type = townBlock.getType().equals(TownBlockType.RESIDENTIAL) ? " " : townBlock.getType().getName(); @@ -120,6 +124,7 @@ public static void updatePerms(Player p, WorldCoord worldCoord) { // Set the values to our Scoreboard's teams. board.getObjective(HUD_OBJECTIVE).setDisplayName(HUDManager.check(title)); + board.getTeam(TEAM_DISTRICT_NAME).setSuffix(districtName); board.getTeam(TEAM_PLOT_NAME).setSuffix(plotName); board.getTeam(TEAM_PLOT_TYPE).setSuffix(type); board.getTeam(TEAM_PLOT_COST).setSuffix(forSale); @@ -164,6 +169,7 @@ private static void clearPerms (Player p) { Scoreboard board = p.getScoreboard(); try { board.getObjective(HUD_OBJECTIVE).setDisplayName(HUDManager.check(getFormattedWildernessName(p.getWorld()))); + board.getTeam(TEAM_DISTRICT_NAME).setSuffix(" "); board.getTeam(TEAM_PLOT_NAME).setSuffix(" "); board.getTeam(TEAM_PLOT_TYPE).setSuffix(" "); board.getTeam(TEAM_PLOT_COST).setSuffix(" "); @@ -206,6 +212,7 @@ public static void toggleOn (Player p) { private static void initializeScoreboard(Translator translator, Scoreboard board) { String PERM_HUD_TITLE = GOLD + ""; + String districtName_entry = ""; String plotName_entry = ""; String keyPlotType_entry = DARK_GREEN + translator.of("msg_perm_hud_plot_type"); String forSale_entry = DARK_GREEN + translator.of("msg_perm_hud_plot_for_sale") + GRAY; @@ -233,6 +240,7 @@ private static void initializeScoreboard(Translator translator, Scoreboard board obj.setDisplaySlot(DisplaySlot.SIDEBAR); obj.setDisplayName(PERM_HUD_TITLE); //register teams + Team districtName = board.registerNewTeam(TEAM_DISTRICT_NAME); Team plotName = board.registerNewTeam(TEAM_PLOT_NAME); Team keyPlotType = board.registerNewTeam(TEAM_PLOT_TYPE); Team forSaleTitle = board.registerNewTeam(TEAM_PLOT_COST); @@ -254,6 +262,7 @@ private static void initializeScoreboard(Translator translator, Scoreboard board Team keyAlly = board.registerNewTeam(TEAM_ALLY); //add each team as an entry (this sets the prefix to each line of the HUD.) + districtName.addEntry(districtName_entry); plotName.addEntry(plotName_entry); keyPlotType.addEntry(keyPlotType_entry); forSaleTitle.addEntry(forSale_entry); @@ -275,6 +284,7 @@ private static void initializeScoreboard(Translator translator, Scoreboard board keyAlly.addEntry(keyAlly_entry); //set scores for positioning + obj.getScore(districtName_entry).setScore(17); obj.getScore(plotName_entry).setScore(16); obj.getScore(keyPlotType_entry).setScore(15); obj.getScore(forSale_entry).setScore(14);