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.Injector; 022import com.plotsquared.core.PlotSquared; 023import com.plotsquared.core.configuration.Settings; 024import com.plotsquared.core.configuration.caption.TranslatableCaption; 025import com.plotsquared.core.location.Location; 026import com.plotsquared.core.permissions.Permission; 027import com.plotsquared.core.player.ConsolePlayer; 028import com.plotsquared.core.player.MetaDataAccess; 029import com.plotsquared.core.player.PlayerMetaDataKeys; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.Plot; 032import com.plotsquared.core.plot.PlotArea; 033import com.plotsquared.core.plot.world.SinglePlotArea; 034import com.plotsquared.core.util.EconHandler; 035import com.plotsquared.core.util.PlotExpression; 036import com.plotsquared.core.util.task.RunnableVal2; 037import com.plotsquared.core.util.task.RunnableVal3; 038import net.kyori.adventure.text.Component; 039import net.kyori.adventure.text.minimessage.tag.Tag; 040import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 041import org.apache.logging.log4j.LogManager; 042import org.apache.logging.log4j.Logger; 043 044import java.util.Arrays; 045import java.util.LinkedList; 046import java.util.List; 047import java.util.Objects; 048import java.util.Optional; 049import java.util.concurrent.CompletableFuture; 050 051import javax.annotation.Nonnull; 052import javax.annotation.Nullable; 053 054/** 055 * PlotSquared command class. 056 */ 057@CommandDeclaration(command = "plot", 058 aliases = {"plots", "p", "plotsquared", "plot2", "p2", "ps", "2", "plotme", "plotz", "ap"}) 059public class MainCommand extends Command { 060 061 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + MainCommand.class.getSimpleName()); 062 063 private static MainCommand instance; 064 public Help help; 065 public Toggle toggle; 066 067 private MainCommand() { 068 super(null, true); 069 instance = this; 070 } 071 072 public static MainCommand getInstance() { 073 if (instance == null) { 074 instance = new MainCommand(); 075 076 final Injector injector = PlotSquared.platform().injector(); 077 final List<Class<? extends Command>> commands = new LinkedList<>(); 078 commands.add(Caps.class); 079 commands.add(Buy.class); 080 if (Settings.Web.LEGACY_WEBINTERFACE) { 081 LOGGER.warn("Legacy webinterface is used. Please note that it will be removed in future."); 082 } 083 commands.add(Load.class); 084 commands.add(Confirm.class); 085 commands.add(Template.class); 086 commands.add(Download.class); 087 commands.add(Setup.class); 088 commands.add(Area.class); 089 commands.add(DebugSaveTest.class); 090 commands.add(DebugLoadTest.class); 091 commands.add(CreateRoadSchematic.class); 092 commands.add(DebugAllowUnsafe.class); 093 commands.add(RegenAllRoads.class); 094 commands.add(Claim.class); 095 commands.add(Auto.class); 096 commands.add(HomeCommand.class); 097 commands.add(Visit.class); 098 commands.add(Set.class); 099 commands.add(Clear.class); 100 commands.add(Delete.class); 101 commands.add(Trust.class); 102 commands.add(Add.class); 103 commands.add(Leave.class); 104 commands.add(Deny.class); 105 commands.add(Remove.class); 106 commands.add(Info.class); 107 commands.add(Near.class); 108 commands.add(ListCmd.class); 109 commands.add(Debug.class); 110 commands.add(SchematicCmd.class); 111 commands.add(PluginCmd.class); 112 commands.add(Purge.class); 113 commands.add(Reload.class); 114 commands.add(Merge.class); 115 commands.add(DebugPaste.class); 116 commands.add(Unlink.class); 117 commands.add(Kick.class); 118 commands.add(Inbox.class); 119 commands.add(Comment.class); 120 commands.add(DatabaseCommand.class); 121 commands.add(Swap.class); 122 commands.add(Music.class); 123 commands.add(DebugRoadRegen.class); 124 commands.add(DebugExec.class); 125 commands.add(FlagCommand.class); 126 commands.add(Target.class); 127 commands.add(Move.class); 128 commands.add(Condense.class); 129 commands.add(Copy.class); 130 commands.add(Trim.class); 131 commands.add(Done.class); 132 commands.add(Continue.class); 133 commands.add(Middle.class); 134 commands.add(Grant.class); 135 commands.add(Owner.class); 136 commands.add(Desc.class); 137 commands.add(Biome.class); 138 commands.add(Alias.class); 139 commands.add(SetHome.class); 140 commands.add(Cluster.class); 141 commands.add(DebugImportWorlds.class); 142 commands.add(Backup.class); 143 144 if (Settings.Ratings.USE_LIKES) { 145 commands.add(Like.class); 146 commands.add(Dislike.class); 147 } else { 148 commands.add(Rate.class); 149 } 150 151 for (final Class<? extends Command> command : commands) { 152 try { 153 injector.getInstance(command); 154 } catch (final Exception e) { 155 LOGGER.error("Failed to register command {}", command.getCanonicalName(), e); 156 } 157 } 158 159 // Referenced commands 160 instance.toggle = injector.getInstance(Toggle.class); 161 instance.help = new Help(instance); 162 } 163 return instance; 164 } 165 166 public static boolean onCommand(final PlotPlayer<?> player, String... args) { 167 final EconHandler econHandler = PlotSquared.platform().econHandler(); 168 if (args.length >= 1 && args[0].contains(":")) { 169 String[] split2 = args[0].split(":"); 170 if (split2.length == 2) { 171 // Ref: c:v, this will push value to the last spot in the array 172 // ex. /p h:2 SomeUsername 173 // > /p h SomeUsername 2 174 String[] tmp = new String[args.length + 1]; 175 tmp[0] = split2[0]; 176 tmp[args.length] = split2[1]; 177 if (args.length >= 2) { 178 System.arraycopy(args, 1, tmp, 1, args.length - 1); 179 } 180 args = tmp; 181 } 182 } 183 try { 184 getInstance().execute(player, args, new RunnableVal3<>() { 185 @Override 186 public void run(final Command cmd, final Runnable success, final Runnable failure) { 187 if (cmd.hasConfirmation(player)) { 188 CmdConfirm.addPending(player, cmd.getUsage(), () -> { 189 PlotArea area = player.getApplicablePlotArea(); 190 if (area != null && econHandler.isEnabled(area) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { 191 PlotExpression priceEval = 192 area.getPrices().get(cmd.getFullId()); 193 double price = priceEval != null ? priceEval.evaluate(0d) : 0d; 194 if (econHandler.getMoney(player) < price) { 195 if (failure != null) { 196 failure.run(); 197 } 198 return; 199 } 200 } 201 if (success != null) { 202 success.run(); 203 } 204 }); 205 return; 206 } 207 PlotArea area = player.getApplicablePlotArea(); 208 if (area != null && econHandler.isEnabled(area) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { 209 PlotExpression priceEval = area.getPrices().get(cmd.getFullId()); 210 double price = priceEval != null ? priceEval.evaluate(0d) : 0d; 211 if (price != 0d && econHandler.getMoney(player) < price) { 212 if (failure != null) { 213 failure.run(); 214 } 215 return; 216 } 217 } 218 if (success != null) { 219 success.run(); 220 } 221 } 222 }, new RunnableVal2<>() { 223 @Override 224 public void run(Command cmd, CommandResult result) { 225 // Post command stuff!? 226 } 227 }).thenAccept(result -> { 228 // TODO: Something with the command result 229 }); 230 } catch (CommandException e) { 231 e.perform(player); 232 } 233 // Always true 234 return true; 235 } 236 237 @Override 238 public CompletableFuture<Boolean> execute( 239 final PlotPlayer<?> player, String[] args, 240 RunnableVal3<Command, Runnable, Runnable> confirm, 241 RunnableVal2<Command, CommandResult> whenDone 242 ) { 243 prepareArguments(new CommandExecutionData(player, args, confirm, whenDone, null)) 244 .thenCompose(executionData -> { 245 if (executionData.isEmpty()) { 246 return CompletableFuture.completedFuture(false); 247 } 248 var data = executionData.get(); 249 try { 250 return super.execute(data.player(), data.args(), data.confirm(), data.whenDone()); 251 } catch (CommandException e) { 252 throw e; 253 } catch (Throwable e) { 254 LOGGER.error("A error occurred while executing plot command", e); 255 String message = e.getMessage(); 256 if (message != null) { 257 data.player().sendMessage( 258 TranslatableCaption.of("errors.error"), 259 TagResolver.resolver("value", Tag.inserting(Component.text(message))) 260 ); 261 } else { 262 data.player().sendMessage( 263 TranslatableCaption.of("errors.error_console")); 264 } 265 } finally { 266 if (data.postCommandData() != null) { 267 resetCommandScope(data.player(), data.postCommandData()); 268 } 269 } 270 return CompletableFuture.completedFuture(true); 271 }); 272 return CompletableFuture.completedFuture(true); 273 } 274 275 private CompletableFuture<Optional<CommandExecutionData>> prepareArguments(CommandExecutionData data) { 276 if (data.args().length >= 2) { 277 PlotArea area = data.player().getApplicablePlotArea(); 278 Plot newPlot = Plot.fromString(area, data.args()[0], data.player()); 279 return preparePlotArgument(newPlot, data, area) 280 .thenApply(d -> d.flatMap(x -> prepareFlagArgument(x, area))); 281 } else { 282 return CompletableFuture.completedFuture(Optional.of(data)); 283 } 284 } 285 286 private CompletableFuture<Optional<CommandExecutionData>> preparePlotArgument(@Nullable Plot newPlot, 287 @Nonnull CommandExecutionData data, 288 @Nullable PlotArea area) { 289 if (newPlot != null && (data.player() instanceof ConsolePlayer 290 || (area != null && area.equals(newPlot.getArea())) 291 || data.player().hasPermission(Permission.PERMISSION_ADMIN) 292 || data.player().hasPermission(Permission.PERMISSION_ADMIN_AREA_SUDO)) 293 && !newPlot.isDenied(data.player().getUUID())) { 294 return fetchPlotCenterLocation(newPlot) 295 .thenApply(newLoc -> { 296 if (!data.player().canTeleport(newLoc)) { 297 data.player().sendMessage(TranslatableCaption.of("border.denied")); 298 return Optional.empty(); 299 } 300 // Save meta 301 var originalCommandMeta = setCommandScope(data.player(), new TemporaryCommandMeta(newLoc, newPlot)); 302 return Optional.of(new CommandExecutionData( 303 data.player(), 304 Arrays.copyOfRange(data.args(), 1, data.args().length), // Trimmed command 305 data.confirm(), 306 data.whenDone(), 307 originalCommandMeta 308 )); 309 }); 310 } 311 return CompletableFuture.completedFuture(Optional.of(data)); 312 } 313 314 private Optional<CommandExecutionData> prepareFlagArgument(@Nonnull CommandExecutionData data, @Nonnull PlotArea area) { 315 if (data.args().length >= 2 && !data.args()[0].isEmpty() && data.args()[0].charAt(0) == '-') { 316 if ("f".equals(data.args()[0].substring(1))) { 317 return Optional.of(new CommandExecutionData( 318 data.player(), 319 Arrays.copyOfRange(data.args(), 1, data.args().length), // Trimmed command 320 createForcedConfirmation(data.player(), area), 321 data.whenDone(), 322 data.postCommandData() 323 )); 324 } else { 325 data.player().sendMessage(TranslatableCaption.of("errors.invalid_command_flag")); 326 return Optional.empty(); 327 } 328 } 329 return Optional.of(data); 330 } 331 332 private CompletableFuture<Location> fetchPlotCenterLocation(Plot plot) { 333 if (plot.getArea() instanceof SinglePlotArea && !plot.isLoaded()) { 334 return CompletableFuture.completedFuture(Location.at("", 0, 0, 0)); 335 } 336 CompletableFuture<Location> future = new CompletableFuture<>(); 337 plot.getCenter(future::complete); 338 return future; 339 } 340 341 private @Nonnull RunnableVal3<Command, Runnable, Runnable> createForcedConfirmation(@Nonnull PlotPlayer<?> player, 342 @Nullable PlotArea area) { 343 return new RunnableVal3<>() { 344 @Override 345 public void run(Command cmd, Runnable success, Runnable failure) { 346 if (area != null && PlotSquared.platform().econHandler().isEnabled(area) 347 && Optional.of(area.getPrices().get(cmd.getFullId())) 348 .map(priceEval -> priceEval.evaluate(0d)) 349 .filter(price -> price != 0d) 350 .filter(price -> PlotSquared.platform().econHandler().getMoney(player) < price) 351 .isPresent()) { 352 if (failure != null) { 353 failure.run(); 354 } 355 return; 356 } 357 if (success != null) { 358 success.run(); 359 } 360 } 361 }; 362 } 363 364 private @Nonnull TemporaryCommandMeta setCommandScope(@Nonnull PlotPlayer<?> player, @Nonnull TemporaryCommandMeta commandMeta) { 365 Objects.requireNonNull(commandMeta.location()); 366 Objects.requireNonNull(commandMeta.plot()); 367 Location location; 368 Plot plot; 369 try (final MetaDataAccess<Location> locationMetaDataAccess 370 = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) { 371 location = locationMetaDataAccess.get().orElse(null); 372 locationMetaDataAccess.set(commandMeta.location()); 373 } 374 try (final MetaDataAccess<Plot> plotMetaDataAccess 375 = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { 376 plot = plotMetaDataAccess.get().orElse(null); 377 plotMetaDataAccess.set(commandMeta.plot()); 378 } 379 return new TemporaryCommandMeta(location, plot); 380 } 381 382 private void resetCommandScope(@Nonnull PlotPlayer<?> player, @Nonnull TemporaryCommandMeta commandMeta) { 383 try (final MetaDataAccess<Location> locationMetaDataAccess 384 = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) { 385 if (commandMeta.location() == null) { 386 locationMetaDataAccess.remove(); 387 } else { 388 locationMetaDataAccess.set(commandMeta.location()); 389 } 390 } 391 try (final MetaDataAccess<Plot> plotMetaDataAccess 392 = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { 393 if (commandMeta.plot() == null) { 394 plotMetaDataAccess.remove(); 395 } else { 396 plotMetaDataAccess.set(commandMeta.plot()); 397 } 398 } 399 } 400 401 private record CommandExecutionData(@Nonnull PlotPlayer<?> player, @Nonnull String[] args, 402 @Nonnull RunnableVal3<Command, Runnable, Runnable> confirm, 403 @Nonnull RunnableVal2<Command, CommandResult> whenDone, 404 @Nullable TemporaryCommandMeta postCommandData) {} 405 406 private record TemporaryCommandMeta(@Nullable Location location, @Nullable Plot plot) {} 407 408 @Override 409 public boolean canExecute(PlotPlayer<?> player, boolean message) { 410 return true; 411 } 412 413}