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}