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.command; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.configuration.caption.TranslatableCaption; 023import com.plotsquared.core.player.PlotPlayer; 024import com.plotsquared.core.plot.Plot; 025import com.plotsquared.core.plot.PlotArea; 026import com.plotsquared.core.plot.PlotId; 027import com.plotsquared.core.plot.world.PlotAreaManager; 028import com.plotsquared.core.util.MathMan; 029import com.plotsquared.core.util.WorldUtil; 030import com.plotsquared.core.util.task.TaskManager; 031import com.plotsquared.core.util.task.TaskTime; 032import net.kyori.adventure.text.minimessage.Template; 033import org.checkerframework.checker.nullness.qual.NonNull; 034 035import java.util.ArrayList; 036import java.util.Collection; 037import java.util.HashSet; 038import java.util.Iterator; 039import java.util.List; 040import java.util.Set; 041import java.util.concurrent.ExecutionException; 042import java.util.concurrent.atomic.AtomicBoolean; 043 044@CommandDeclaration(command = "condense", 045 permission = "plots.admin", 046 usage = "/plot condense <area> <start|stop|info> [radius]", 047 category = CommandCategory.ADMINISTRATION, 048 requiredType = RequiredType.CONSOLE) 049public class Condense extends SubCommand { 050 051 public static boolean TASK = false; 052 053 private final PlotAreaManager plotAreaManager; 054 private final WorldUtil worldUtil; 055 056 @Inject 057 public Condense( 058 final @NonNull PlotAreaManager plotAreaManager, 059 final @NonNull WorldUtil worldUtil 060 ) { 061 this.plotAreaManager = plotAreaManager; 062 this.worldUtil = worldUtil; 063 } 064 065 @SuppressWarnings("unchecked") 066 @Override 067 public boolean onCommand(final PlotPlayer<?> player, String[] args) { 068 if (args.length != 2 && args.length != 3) { 069 player.sendMessage( 070 TranslatableCaption.of("commandconfig.command_syntax"), 071 Template.of("value", "/plot condense <area> <start | stop | info> [radius]") 072 ); 073 return false; 074 } 075 PlotArea area = this.plotAreaManager.getPlotAreaByString(args[0]); 076 if (area == null || !this.worldUtil.isWorld(area.getWorldName())) { 077 player.sendMessage(TranslatableCaption.of("invalid.invalid_area")); 078 return false; 079 } 080 switch (args[1].toLowerCase()) { 081 case "start" -> { 082 if (args.length == 2) { 083 player.sendMessage( 084 TranslatableCaption.of("commandconfig.command_syntax"), 085 Template.of("value", "/plot condense" + area + " start <radius>") 086 ); 087 return false; 088 } 089 if (Condense.TASK) { 090 player.sendMessage(TranslatableCaption.of("condense.task_already_started")); 091 return false; 092 } 093 if (!MathMan.isInteger(args[2])) { 094 player.sendMessage(TranslatableCaption.of("condense.invalid_radius")); 095 return false; 096 } 097 int radius = Integer.parseInt(args[2]); 098 099 final List<Plot> plots = new ArrayList<>(area.getPlots()); 100 // remove non base plots 101 Iterator<Plot> iterator = plots.iterator(); 102 int maxSize = 0; 103 ArrayList<Integer> sizes = new ArrayList<>(); 104 while (iterator.hasNext()) { 105 Plot plot = iterator.next(); 106 if (!plot.isBasePlot()) { 107 iterator.remove(); 108 continue; 109 } 110 int size = plot.getConnectedPlots().size(); 111 if (size > maxSize) { 112 maxSize = size; 113 } 114 sizes.add(size - 1); 115 } 116 // Sort plots by size (buckets?)] 117 ArrayList<Plot>[] buckets = new ArrayList[maxSize]; 118 for (int i = 0; i < plots.size(); i++) { 119 Plot plot = plots.get(i); 120 int size = sizes.get(i); 121 ArrayList<Plot> array = buckets[size]; 122 if (array == null) { 123 array = new ArrayList<>(); 124 buckets[size] = array; 125 } 126 array.add(plot); 127 } 128 final ArrayList<Plot> allPlots = new ArrayList<>(plots.size()); 129 for (int i = buckets.length - 1; i >= 0; i--) { 130 ArrayList<Plot> array = buckets[i]; 131 if (array != null) { 132 allPlots.addAll(array); 133 } 134 } 135 int size = allPlots.size(); 136 int minimumRadius = (int) Math.ceil(Math.sqrt(size) / 2 + 1); 137 if (radius < minimumRadius) { 138 player.sendMessage(TranslatableCaption.of("condense.radius_too_small")); 139 return false; 140 } 141 List<PlotId> toMove = new ArrayList<>(getPlots(allPlots, radius)); 142 final List<PlotId> free = new ArrayList<>(); 143 PlotId start = PlotId.of(0, 0); 144 while (start.getX() <= minimumRadius && start.getY() <= minimumRadius) { 145 Plot plot = area.getPlotAbs(start); 146 if (plot != null && !plot.hasOwner()) { 147 free.add(plot.getId()); 148 } 149 start = start.getNextId(); 150 } 151 if (free.isEmpty() || toMove.isEmpty()) { 152 player.sendMessage(TranslatableCaption.of("condense.no_free_plots_found")); 153 return false; 154 } 155 player.sendMessage(TranslatableCaption.of("condense.task_started")); 156 Condense.TASK = true; 157 Runnable run = new Runnable() { 158 @Override 159 public void run() { 160 if (!Condense.TASK) { 161 player.sendMessage(TranslatableCaption.of("debugexec.task_cancelled")); 162 } 163 if (allPlots.isEmpty()) { 164 Condense.TASK = false; 165 player.sendMessage(TranslatableCaption.of("condense.task_complete")); 166 return; 167 } 168 final Runnable task = this; 169 final Plot origin = allPlots.remove(0); 170 int i = 0; 171 while (free.size() > i) { 172 final Plot possible = origin.getArea().getPlotAbs(free.get(i)); 173 if (possible.hasOwner()) { 174 free.remove(i); 175 continue; 176 } 177 i++; 178 final AtomicBoolean result = new AtomicBoolean(false); 179 try { 180 result.set(origin.getPlotModificationManager().move(possible, player, () -> { 181 if (result.get()) { 182 player.sendMessage( 183 TranslatableCaption.of("condense.moving"), 184 Template.of("origin", String.valueOf(origin)), 185 Template.of("possible", String.valueOf(possible)) 186 ); 187 TaskManager.runTaskLater(task, TaskTime.ticks(1L)); 188 } 189 }, false).get()); 190 } catch (InterruptedException | ExecutionException e) { 191 e.printStackTrace(); 192 } 193 if (result.get()) { 194 break; 195 } 196 } 197 if (free.isEmpty()) { 198 Condense.TASK = false; 199 player.sendMessage(TranslatableCaption.of("condense.task_failed")); 200 return; 201 } 202 if (i >= free.size()) { 203 player.sendMessage( 204 TranslatableCaption.of("condense.skipping"), 205 Template.of("plot", String.valueOf(origin)) 206 ); 207 } 208 } 209 }; 210 TaskManager.runTaskAsync(run); 211 return true; 212 } 213 case "stop" -> { 214 if (!Condense.TASK) { 215 player.sendMessage(TranslatableCaption.of("condense.task_stopped")); 216 return false; 217 } 218 Condense.TASK = false; 219 player.sendMessage(TranslatableCaption.of("condense.task_stopped")); 220 return true; 221 } 222 case "info" -> { 223 if (args.length == 2) { 224 player.sendMessage( 225 TranslatableCaption.of("commandconfig.command_syntax"), 226 Template.of("value", "/plot condense " + area + " info <radius>") 227 ); 228 return false; 229 } 230 if (!MathMan.isInteger(args[2])) { 231 player.sendMessage(TranslatableCaption.of("condense.invalid_radius")); 232 return false; 233 } 234 int radius = Integer.parseInt(args[2]); 235 Collection<Plot> plots = area.getPlots(); 236 int size = plots.size(); 237 int minimumRadius = (int) Math.ceil(Math.sqrt(size) / 2 + 1); 238 if (radius < minimumRadius) { 239 player.sendMessage(TranslatableCaption.of("condense.radius_too_small")); 240 return false; 241 } 242 int maxMove = getPlots(plots, minimumRadius).size(); 243 int userMove = getPlots(plots, radius).size(); 244 player.sendMessage(TranslatableCaption.of("condense.default_eval")); 245 player.sendMessage( 246 TranslatableCaption.of("condense.minimum_radius"), 247 Template.of("minimumRadius", String.valueOf(minimumRadius)) 248 ); 249 player.sendMessage( 250 TranslatableCaption.of("condense.minimum_radius"), 251 Template.of("maxMove", String.valueOf(maxMove)) 252 ); 253 player.sendMessage(TranslatableCaption.of("condense.input_eval")); 254 player.sendMessage( 255 TranslatableCaption.of("condense.input_radius"), 256 Template.of("radius", String.valueOf(radius)) 257 ); 258 player.sendMessage( 259 TranslatableCaption.of("condense.estimated_moves"), 260 Template.of("userMove", String.valueOf(userMove)) 261 ); 262 player.sendMessage(TranslatableCaption.of("condense.eta")); 263 player.sendMessage(TranslatableCaption.of("condense.radius_measured")); 264 return true; 265 } 266 } 267 player.sendMessage( 268 TranslatableCaption.of("commandconfig.command_syntax"), 269 Template.of("value", "/plot condense " + area.getWorldName() + " <start | stop | info> [radius]") 270 ); 271 return false; 272 } 273 274 public Set<PlotId> getPlots(Collection<Plot> plots, int radius) { 275 HashSet<PlotId> outside = new HashSet<>(); 276 for (Plot plot : plots) { 277 if (plot.getId().getX() > radius || plot.getId().getX() < -radius || plot.getId().getY() > radius 278 || plot.getId().getY() < -radius) { 279 outside.add(plot.getId()); 280 } 281 } 282 return outside; 283 } 284 285}