001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.plot.world; 020 021import com.plotsquared.core.PlotSquared; 022import com.plotsquared.core.configuration.ConfigurationNode; 023import com.plotsquared.core.configuration.ConfigurationSection; 024import com.plotsquared.core.configuration.ConfigurationUtil; 025import com.plotsquared.core.configuration.caption.TranslatableCaption; 026import com.plotsquared.core.configuration.file.YamlConfiguration; 027import com.plotsquared.core.generator.GridPlotWorld; 028import com.plotsquared.core.generator.SingleWorldGenerator; 029import com.plotsquared.core.inject.annotations.WorldConfig; 030import com.plotsquared.core.listener.PlotListener; 031import com.plotsquared.core.location.BlockLoc; 032import com.plotsquared.core.location.Location; 033import com.plotsquared.core.plot.Plot; 034import com.plotsquared.core.plot.PlotAreaType; 035import com.plotsquared.core.plot.PlotId; 036import com.plotsquared.core.plot.PlotManager; 037import com.plotsquared.core.plot.PlotSettings; 038import com.plotsquared.core.plot.flag.FlagContainer; 039import com.plotsquared.core.queue.DelegateQueueCoordinator; 040import com.plotsquared.core.queue.GlobalBlockQueue; 041import com.plotsquared.core.queue.QueueCoordinator; 042import com.plotsquared.core.setup.PlotAreaBuilder; 043import com.plotsquared.core.setup.SettingsNodesWrapper; 044import com.plotsquared.core.util.EventDispatcher; 045import com.plotsquared.core.util.WorldUtil; 046import com.plotsquared.core.util.task.TaskManager; 047import org.checkerframework.checker.nullness.qual.NonNull; 048import org.checkerframework.checker.nullness.qual.Nullable; 049 050import java.io.IOException; 051import java.nio.file.FileVisitResult; 052import java.nio.file.Files; 053import java.nio.file.Path; 054import java.nio.file.SimpleFileVisitor; 055import java.nio.file.attribute.BasicFileAttributes; 056 057public class SinglePlotArea extends GridPlotWorld { 058 059 @SuppressWarnings({"unused", "FieldCanBeLocal"}) 060 private final EventDispatcher eventDispatcher; 061 @SuppressWarnings({"unused", "FieldCanBeLocal"}) 062 private final PlotListener plotListener; 063 public boolean VOID = false; 064 065 public SinglePlotArea( 066 final @NonNull PlotAreaManager plotAreaManager, 067 final @NonNull EventDispatcher eventDispatcher, 068 final @NonNull PlotListener plotListener, 069 @WorldConfig final @NonNull YamlConfiguration worldConfiguration, 070 final @NonNull GlobalBlockQueue globalBlockQueue 071 ) { 072 super("*", null, new SingleWorldGenerator(plotAreaManager), null, null, 073 worldConfiguration, globalBlockQueue 074 ); 075 this.eventDispatcher = eventDispatcher; 076 this.plotListener = plotListener; 077 this.setAllowSigns(false); 078 this.setDefaultHome(new BlockLoc(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE)); 079 } 080 081 /** 082 * Returns true if the given string matches the naming system used to identify single plot worlds 083 * e.g. -1_5 represents plot id *;-1;5. "*" being the plot area name given to single plot world 084 * {@link com.plotsquared.core.plot.PlotArea}. 085 * 086 * @since 6.1.4 087 */ 088 public static boolean isSinglePlotWorld(String worldName) { 089 int len = worldName.length(); 090 int separator = 0; 091 for (int i = 0; i < len; i++) { 092 switch (worldName.charAt(i)) { 093 case '_': 094 separator++; 095 break; 096 case '-': 097 case '0': 098 case '1': 099 case '2': 100 case '3': 101 case '4': 102 case '5': 103 case '6': 104 case '7': 105 case '8': 106 case '9': 107 break; 108 default: 109 return false; 110 } 111 } 112 return separator == 1; 113 } 114 115 @NonNull 116 @Override 117 protected PlotManager createManager() { 118 return new SinglePlotManager(this); 119 } 120 121 /** 122 * Single Plot Areas must provide a different QueueCoordinator than other plot areas, otherwise plot deletion fails. 123 * When deleting plots, the PlotModificationManager retrieves a QueueCoordinator from the associated PlotArea, which 124 * accesses the plots world. For SinglePlotAreas this world is null at this point, as the whole world is deleted (and the 125 * areas world name being `*`). 126 * <br> 127 * The queue doesn't know <i>when</i> it's used, so this always provides a QueueCoordinator that doesn't do anything and 128 * doesn't contain any tasks. This isn't really bad, as SinglePlotAreas don't utilize the Queue as of now. 129 * 130 * @return a QueueCoordinator for SinglePlotAreas 131 */ 132 @Override 133 public QueueCoordinator getQueue() { 134 return new DelegateQueueCoordinator(null); 135 } 136 137 @Override 138 public void loadConfiguration(ConfigurationSection config) { 139 VOID = config.getBoolean("void", false); 140 } 141 142 @Override 143 public void saveConfiguration(ConfigurationSection config) { 144 super.saveConfiguration(config); 145 } 146 147 public void loadWorld(final PlotId id) { 148 String worldName = id.toUnderscoreSeparatedString(); 149 if (PlotSquared.platform().worldUtil().isWorld(worldName)) { 150 return; 151 } 152 PlotAreaBuilder builder = PlotAreaBuilder.newBuilder() 153 .plotManager("PlotSquared:single") 154 .generatorName("PlotSquared:single") 155 .plotAreaType(getType()) 156 .terrainType(getTerrain()) 157 .settingsNodesWrapper(new SettingsNodesWrapper(new ConfigurationNode[0], null)) 158 .worldName(worldName); 159 160 Path dimensionRoot = PlotSquared.platform().worldContainer().toPath(); 161 if (WorldUtil.isModernServerLevelStructure()) { 162 dimensionRoot = dimensionRoot.resolve("dimensions").resolve("minecraft"); 163 } 164 Path destination = dimensionRoot.resolve(worldName); 165 166 {// convert old 167 Path old = dimensionRoot.resolve(id.toCommaSeparatedString()); 168 if (!Files.exists(old)) { 169 old = dimensionRoot.resolve(id.toSeparatedString(".")); 170 } 171 if (Files.exists(old)) { 172 try { 173 Files.move(old, destination); 174 } catch (IOException e) { 175 throw new RuntimeException(e); 176 } 177 } 178 } 179 // Duplicate 0;0 180 if (builder.plotAreaType() != PlotAreaType.NORMAL) { 181 if (!Files.exists(destination)) { 182 Path src = dimensionRoot.resolve("0_0"); 183 if (Files.exists(src)) { 184 try { 185 Files.createDirectories(destination); 186 } catch (IOException e) { 187 throw new RuntimeException(e); 188 } 189 Path levelDat = src.resolve("level.dat"); 190 if (Files.exists(levelDat)) { 191 try { 192 Files.copy(levelDat, destination.resolve(levelDat.getFileName())); 193 Path data = src.resolve("data"); 194 if (Files.exists(data)) { 195 Path dataDest = destination.resolve("data"); 196 Files.createDirectories(dataDest); 197 Files.walkFileTree(data, new RecursiveDirectoryCopyVisitor(dataDest)); 198 } 199 } catch (IOException exception) { 200 exception.printStackTrace(); 201 } 202 } 203 } 204 } 205 } 206 207 try { 208 TaskManager.getPlatformImplementation().sync(() -> { 209 final String name = id.toUnderscoreSeparatedString(); 210 if (!PlotSquared.platform().worldUtil().isWorld(name)) { 211 PlotSquared.platform().setupUtils().setupWorld(builder); 212 } 213 return null; 214 }); 215 } catch (final Exception e) { 216 e.printStackTrace(); 217 } 218 219 // String worldName = plot.getWorldName(); 220 // World world = Bukkit.getWorld(worldName); 221 // if (world != null) { 222 // return world; 223 // } 224 // WorldCreator wc = new WorldCreator(worldName); 225 // wc.generator("PlotSquared:single"); 226 // wc.environment(World.Environment.NORMAL); 227 // wc.type(WorldType.FLAT); 228 // return AsyncWorld.create(wc); 229 } 230 231 private static final class RecursiveDirectoryCopyVisitor extends SimpleFileVisitor<Path> { 232 233 private final Path target; 234 private Path base; 235 236 public RecursiveDirectoryCopyVisitor(Path target) { 237 this.target = target; 238 } 239 240 @Override 241 public @NonNull FileVisitResult preVisitDirectory( 242 @NonNull final Path dir, 243 @NonNull final BasicFileAttributes attrs 244 ) throws IOException { 245 if (this.base == null) { 246 // first iteration, root 247 this.base = dir; 248 } else { 249 Files.createDirectories(this.target.resolve(this.base.relativize(dir))); 250 } 251 return FileVisitResult.CONTINUE; 252 } 253 254 @Override 255 public @NonNull FileVisitResult visitFile(@NonNull final Path file, @NonNull final BasicFileAttributes attrs) throws 256 IOException { 257 Files.copy(file, this.target.resolve(this.base.relativize(file))); 258 return FileVisitResult.CONTINUE; 259 } 260 261 } 262 263 @Override 264 public ConfigurationNode[] getSettingNodes() { 265 return new ConfigurationNode[]{ 266 new ConfigurationNode( 267 "void", 268 this.VOID, 269 TranslatableCaption.of("setup.singleplotarea_void_world"), 270 ConfigurationUtil.BOOLEAN 271 )}; 272 } 273 274 @Nullable 275 @Override 276 public Plot getOwnedPlot(final @NonNull Location location) { 277 PlotId pid = PlotId.fromStringOrNull(location.getWorldName()); 278 Plot plot = pid == null ? null : this.plots.get(pid); 279 return plot == null ? null : plot.getBasePlot(false); 280 } 281 282 @Nullable 283 @Override 284 public Plot getOwnedPlotAbs(@NonNull Location location) { 285 PlotId pid = PlotId.fromStringOrNull(location.getWorldName()); 286 return pid == null ? null : plots.get(pid); 287 } 288 289 @Nullable 290 @Override 291 public Plot getPlot(final @NonNull Location location) { 292 PlotId pid = PlotId.fromStringOrNull(location.getWorldName()); 293 return pid == null ? null : getPlot(pid); 294 } 295 296 @Nullable 297 @Override 298 public Plot getPlotAbs(final @NonNull Location location) { 299 final PlotId pid = PlotId.fromStringOrNull(location.getWorldName()); 300 return pid == null ? null : getPlotAbs(pid); 301 } 302 303 public boolean addPlot(@NonNull Plot plot) { 304 plot = adapt(plot); 305 return super.addPlot(plot); 306 } 307 308 @Override 309 public boolean addPlotAbs(@NonNull Plot plot) { 310 plot = adapt(plot); 311 return super.addPlotAbs(plot); 312 } 313 314 @Override 315 public boolean addPlotIfAbsent(@NonNull Plot plot) { 316 plot = adapt(plot); 317 return super.addPlotIfAbsent(plot); 318 } 319 320 @Override 321 public boolean allowSigns() { 322 return false; // do not create signs for single plots 323 } 324 325 @SuppressWarnings("deprecation") 326 protected Plot adapt(Plot p) { 327 if (p instanceof SinglePlot) { 328 return p; 329 } 330 PlotSettings s = p.getSettings(); 331 332 final FlagContainer oldContainer = p.getFlagContainer(); 333 p = new SinglePlot(p.getId(), p.getOwnerAbs(), p.getTrusted(), p.getMembers(), 334 p.getDenied(), s.getAlias(), s.getPosition(), null, this, s.getMerged(), 335 p.getTimestamp(), p.temp 336 ); 337 p.getFlagContainer().addAll(oldContainer); 338 339 return p; 340 } 341 342 public @Nullable Plot getPlotAbs(final @NonNull PlotId id) { 343 Plot plot = getOwnedPlotAbs(id); 344 if (plot == null) { 345 return new SinglePlot(this, id); 346 } 347 return plot; 348 } 349 350 public @Nullable Plot getPlot(@NonNull PlotId id) { 351 // TODO 352 Plot plot = getOwnedPlotAbs(id); 353 if (plot == null) { 354 return new SinglePlot(this, id); 355 } 356 return plot.getBasePlot(false); 357 } 358 359}