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