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.services.plots;
020
021import cloud.commandframework.services.types.Service;
022import com.google.common.cache.Cache;
023import com.google.common.cache.CacheBuilder;
024import com.plotsquared.core.player.PlotPlayer;
025import com.plotsquared.core.plot.Plot;
026import com.plotsquared.core.plot.PlotArea;
027import com.plotsquared.core.plot.PlotAreaType;
028import com.plotsquared.core.plot.PlotId;
029import org.checkerframework.checker.nullness.qual.NonNull;
030import org.checkerframework.checker.nullness.qual.Nullable;
031
032import java.util.Collections;
033import java.util.List;
034import java.util.concurrent.TimeUnit;
035import java.util.function.Predicate;
036
037public interface AutoService extends Service<AutoService.AutoQuery, List<Plot>> {
038
039    Cache<PlotId, Plot> plotCandidateCache = CacheBuilder.newBuilder()
040            .expireAfterWrite(20, TimeUnit.SECONDS).build();
041    Object plotLock = new Object();
042
043    final class AutoQuery {
044
045        private final PlotPlayer<?> player;
046        private final PlotId startId;
047        private final int sizeX;
048        private final int sizeZ;
049        private final PlotArea plotArea;
050
051        /**
052         * Crate a new auto query
053         *
054         * @param player   Player to claim for
055         * @param startId  Plot ID to start searching from
056         * @param sizeX    Number of plots along the X axis
057         * @param sizeZ    Number of plots along the Z axis
058         * @param plotArea Plot area to search in
059         */
060        public AutoQuery(
061                final @NonNull PlotPlayer<?> player, final @Nullable PlotId startId,
062                final int sizeX, final int sizeZ, final @NonNull PlotArea plotArea
063        ) {
064            this.player = player;
065            this.startId = startId;
066            this.sizeX = sizeX;
067            this.sizeZ = sizeZ;
068            this.plotArea = plotArea;
069        }
070
071        /**
072         * Get the player that the plots are meant for
073         *
074         * @return Player
075         */
076        public @NonNull PlotPlayer<?> getPlayer() {
077            return this.player;
078        }
079
080        /**
081         * Get the plot ID to start searching from
082         *
083         * @return Start ID
084         */
085        public @Nullable PlotId getStartId() {
086            return this.startId;
087        }
088
089        /**
090         * Get the number of plots along the X axis
091         *
092         * @return Number of plots along the X axis
093         */
094        public int getSizeX() {
095            return this.sizeX;
096        }
097
098        /**
099         * Get the number of plots along the Z axis
100         *
101         * @return Number of plots along the Z axis
102         */
103        public int getSizeZ() {
104            return this.sizeZ;
105        }
106
107        /**
108         * Get the plot area to search in
109         *
110         * @return Plot area
111         */
112        public @NonNull PlotArea getPlotArea() {
113            return this.plotArea;
114        }
115
116    }
117
118
119    final class DefaultAutoService implements AutoService {
120
121        @Override
122        public List<Plot> handle(final @NonNull AutoQuery autoQuery) {
123            return Collections.emptyList();
124        }
125
126    }
127
128
129    final class SinglePlotService implements AutoService, Predicate<AutoQuery> {
130
131        @Nullable
132        @Override
133        public List<Plot> handle(@NonNull AutoQuery autoQuery) {
134            Plot plot;
135            PlotId nextId = autoQuery.getStartId();
136            do {
137                synchronized (plotLock) {
138                    plot = autoQuery.getPlotArea().getNextFreePlot(autoQuery.getPlayer(), nextId);
139                    if (plot != null && plotCandidateCache.getIfPresent(plot.getId()) == null) {
140                        plotCandidateCache.put(plot.getId(), plot);
141                        return Collections.singletonList(plot);
142                    }
143                    // if the plot is already in the cache, we want to make sure we skip it the next time
144                    if (plot != null) {
145                        nextId = plot.getId();
146                    }
147                }
148            } while (plot != null);
149            return null;
150        }
151
152        @Override
153        public boolean test(final @NonNull AutoQuery autoQuery) {
154            return autoQuery.sizeX == 1 && autoQuery.sizeZ == 1;
155        }
156
157    }
158
159
160    final class MultiPlotService implements AutoService, Predicate<AutoQuery> {
161
162        @Override
163        public List<Plot> handle(final @NonNull AutoQuery autoQuery) {
164            /* TODO: Add timeout? */
165            outer:
166            while (true) {
167                synchronized (plotLock) {
168                    final PlotId start =
169                            autoQuery.getPlotArea().getMeta("lastPlot", PlotId.of(0, 0)).getNextId();
170                    final PlotId end = PlotId.of(
171                            start.getX() + autoQuery.getSizeX() - 1,
172                            start.getY() + autoQuery.getSizeZ() - 1
173                    );
174                    final List<Plot> plots =
175                            autoQuery.getPlotArea().canClaim(autoQuery.getPlayer(), start, end);
176                    autoQuery.getPlotArea().setMeta("lastPlot", start); // set entry point for next try
177                    if (plots != null && !plots.isEmpty()) {
178                        for (final Plot plot : plots) {
179                            if (plotCandidateCache.getIfPresent(plot.getId()) != null) {
180                                continue outer;
181                            }
182                            plotCandidateCache.put(plot.getId(), plot);
183                        }
184                        return plots;
185                    }
186                }
187            }
188        }
189
190        @Override
191        public boolean test(final @NonNull AutoQuery autoQuery) {
192            return autoQuery.getPlotArea().getType() != PlotAreaType.PARTIAL;
193        }
194
195    }
196
197}