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 cloud.commandframework.services.ServicePipeline; 022import com.google.inject.Inject; 023import com.plotsquared.core.PlotSquared; 024import com.plotsquared.core.configuration.Settings; 025import com.plotsquared.core.configuration.caption.TranslatableCaption; 026import com.plotsquared.core.database.DBFunc; 027import com.plotsquared.core.events.PlayerAutoPlotEvent; 028import com.plotsquared.core.events.PlotAutoMergeEvent; 029import com.plotsquared.core.events.Result; 030import com.plotsquared.core.permissions.Permission; 031import com.plotsquared.core.permissions.PermissionHandler; 032import com.plotsquared.core.player.MetaDataAccess; 033import com.plotsquared.core.player.PlayerMetaDataKeys; 034import com.plotsquared.core.player.PlotPlayer; 035import com.plotsquared.core.plot.Plot; 036import com.plotsquared.core.plot.PlotArea; 037import com.plotsquared.core.plot.world.PlotAreaManager; 038import com.plotsquared.core.services.plots.AutoQuery; 039import com.plotsquared.core.services.plots.AutoService; 040import com.plotsquared.core.util.EconHandler; 041import com.plotsquared.core.util.EventDispatcher; 042import com.plotsquared.core.util.PlotExpression; 043import com.plotsquared.core.util.task.AutoClaimFinishTask; 044import com.plotsquared.core.util.task.RunnableVal; 045import com.plotsquared.core.util.task.TaskManager; 046import io.leangen.geantyref.TypeToken; 047import net.kyori.adventure.text.Component; 048import net.kyori.adventure.text.minimessage.tag.Tag; 049import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 050import org.checkerframework.checker.nullness.qual.NonNull; 051import org.checkerframework.checker.nullness.qual.Nullable; 052 053import java.util.Collections; 054import java.util.Iterator; 055import java.util.List; 056import java.util.stream.Collectors; 057 058@CommandDeclaration(command = "auto", 059 permission = "plots.auto", 060 category = CommandCategory.CLAIMING, 061 requiredType = RequiredType.NONE, 062 aliases = "a", 063 usage = "/plot auto [length, width]") 064public class Auto extends SubCommand { 065 066 private final PlotAreaManager plotAreaManager; 067 private final EventDispatcher eventDispatcher; 068 private final EconHandler econHandler; 069 private final ServicePipeline servicePipeline; 070 071 @Inject 072 public Auto( 073 final @NonNull PlotAreaManager plotAreaManager, 074 final @NonNull EventDispatcher eventDispatcher, 075 final @NonNull EconHandler econHandler, 076 final @NonNull ServicePipeline servicePipeline 077 ) { 078 this.plotAreaManager = plotAreaManager; 079 this.eventDispatcher = eventDispatcher; 080 this.econHandler = econHandler; 081 this.servicePipeline = servicePipeline; 082 this.servicePipeline.registerServiceType(TypeToken.get(AutoService.class), new AutoService.DefaultAutoService()); 083 final AutoService.MultiPlotService multiPlotService = new AutoService.MultiPlotService(); 084 this.servicePipeline.registerServiceImplementation(AutoService.class, multiPlotService, 085 Collections.singletonList(multiPlotService) 086 ); 087 final AutoService.SinglePlotService singlePlotService = new AutoService.SinglePlotService(); 088 this.servicePipeline.registerServiceImplementation(AutoService.class, singlePlotService, 089 Collections.singletonList(singlePlotService) 090 ); 091 } 092 093 public static boolean checkAllowedPlots( 094 PlotPlayer<?> player, PlotArea plotarea, 095 @Nullable Integer allowedPlots, int sizeX, int sizeZ 096 ) { 097 if (allowedPlots == null) { 098 allowedPlots = player.getAllowedPlots(); 099 } 100 int currentPlots; 101 if (Settings.Limit.GLOBAL) { 102 currentPlots = player.getPlotCount(); 103 } else { 104 currentPlots = player.getPlotCount(plotarea.getWorldName()); 105 } 106 int diff = allowedPlots - currentPlots; 107 if (diff - sizeX * sizeZ < 0) { 108 try (final MetaDataAccess<Integer> metaDataAccess = player.accessPersistentMetaData( 109 PlayerMetaDataKeys.PERSISTENT_GRANTED_PLOTS)) { 110 if (metaDataAccess.isPresent()) { 111 int grantedPlots = metaDataAccess.get().orElse(0); 112 if (diff < 0 && grantedPlots < sizeX * sizeZ) { 113 player.sendMessage( 114 TranslatableCaption.of("permission.cant_claim_more_plots"), 115 TagResolver.resolver("amount", Tag.inserting(Component.text(diff + grantedPlots))) 116 ); 117 return false; 118 } else if (diff >= 0 && grantedPlots + diff < sizeX * sizeZ) { 119 player.sendMessage( 120 TranslatableCaption.of("permission.cant_claim_more_plots"), 121 TagResolver.resolver("amount", Tag.inserting(Component.text(diff + grantedPlots))) 122 ); 123 return false; 124 } else { 125 int left = grantedPlots + diff < 0 ? 0 : diff - sizeX * sizeZ; 126 if (left == 0) { 127 metaDataAccess.remove(); 128 } else { 129 metaDataAccess.set(left); 130 } 131 player.sendMessage( 132 TranslatableCaption.of("economy.removed_granted_plot"), 133 TagResolver.builder() 134 .tag("usedGrants", Tag.inserting(Component.text(grantedPlots - left))) 135 .tag("remainingGrants", Tag.inserting(Component.text(left))) 136 .build() 137 ); 138 } 139 } else { 140 player.sendMessage( 141 TranslatableCaption.of("permission.cant_claim_more_plots"), 142 TagResolver.resolver("amount", Tag.inserting(Component.text(player.getAllowedPlots()))) 143 ); 144 return false; 145 } 146 } 147 } 148 return true; 149 } 150 151 private void claimSingle( 152 final @NonNull PlotPlayer<?> player, final @NonNull Plot plot, 153 final @NonNull PlotArea plotArea, final @Nullable String schematic 154 ) { 155 try (final MetaDataAccess<Boolean> metaDataAccess = 156 player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_AUTO)) { 157 metaDataAccess.set(true); 158 } 159 plot.setOwnerAbs(player.getUUID()); 160 161 final RunnableVal<Plot> runnableVal = new RunnableVal<>() { 162 { 163 this.value = plot; 164 } 165 166 @Override 167 public void run(final Plot plot) { 168 try { 169 TaskManager.getPlatformImplementation().sync( 170 new AutoClaimFinishTask(player, plot, plotArea, schematic, 171 PlotSquared.get().getEventDispatcher() 172 )); 173 } catch (final Exception e) { 174 e.printStackTrace(); 175 } 176 } 177 }; 178 179 DBFunc.createPlotSafe(plot, runnableVal, () -> claimSingle(player, plot, plotArea, schematic)); 180 181 } 182 183 @Override 184 public boolean onCommand(final PlotPlayer<?> player, String[] args) { 185 PlotArea plotarea = player.getApplicablePlotArea(); 186 if (plotarea == null) { 187 final PermissionHandler permissionHandler = PlotSquared.platform().permissionHandler(); 188 if (permissionHandler.hasCapability( 189 PermissionHandler.PermissionHandlerCapability.PER_WORLD_PERMISSIONS)) { 190 for (final PlotArea area : this.plotAreaManager.getAllPlotAreas()) { 191 if (player.hasPermission(area.getWorldName(), "plots.auto")) { 192 if (plotarea != null) { 193 plotarea = null; 194 break; 195 } 196 plotarea = area; 197 } 198 } 199 } 200 if (this.plotAreaManager.getAllPlotAreas().length == 1) { 201 plotarea = this.plotAreaManager.getAllPlotAreas()[0]; 202 } 203 if (plotarea == null) { 204 player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world")); 205 return false; 206 } 207 } 208 int sizeX = 1; 209 int sizeZ = 1; 210 String schematic = null; 211 boolean mega = false; 212 if (args.length > 0) { 213 try { 214 String[] split = args[0].split("[,;]"); 215 if (split.length == 2) { 216 sizeX = Integer.parseInt(split[0]); 217 sizeZ = Integer.parseInt(split[1]); 218 } else { 219 player.sendMessage( 220 TranslatableCaption.of("commandconfig.command_syntax"), 221 TagResolver.resolver("value", Tag.inserting(Component.text(getUsage()))) 222 ); 223 return true; 224 } 225 if (sizeX < 1 || sizeZ < 1) { 226 player.sendMessage(TranslatableCaption.of("error.plot_size_negative")); 227 return true; 228 } 229 if (args.length > 1) { 230 schematic = args[1]; 231 } 232 mega = true; 233 } catch (NumberFormatException ignored) { 234 sizeX = 1; 235 sizeZ = 1; 236 schematic = args[0]; 237 } 238 } 239 PlayerAutoPlotEvent event = this.eventDispatcher 240 .callAuto(player, plotarea, schematic, sizeX, sizeZ); 241 if (event.getEventResult() == Result.DENY) { 242 player.sendMessage( 243 TranslatableCaption.of("events.event_denied"), 244 TagResolver.resolver("value", Tag.inserting(Component.text("Auto claim"))) 245 ); 246 return true; 247 } 248 boolean force = event.getEventResult() == Result.FORCE; 249 sizeX = event.getSizeX(); 250 sizeZ = event.getSizeZ(); 251 schematic = event.getSchematic(); 252 if (!force && mega && !player.hasPermission(Permission.PERMISSION_AUTO_MEGA)) { 253 player.sendMessage( 254 TranslatableCaption.of("permission.no_permission"), 255 TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_AUTO_MEGA)) 256 ); 257 return false; 258 } 259 if (!force && sizeX * sizeZ > Settings.Claim.MAX_AUTO_AREA) { 260 player.sendMessage( 261 TranslatableCaption.of("permission.cant_claim_more_plots_num"), 262 TagResolver.resolver("amount", Tag.inserting(Component.text(Settings.Claim.MAX_AUTO_AREA))) 263 ); 264 return false; 265 } 266 final int allowed_plots = player.getAllowedPlots(); 267 try (final MetaDataAccess<Boolean> metaDataAccess = 268 player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_AUTO)) { 269 if (!force && (metaDataAccess.get().orElse(false) || !checkAllowedPlots(player, 270 plotarea, allowed_plots, sizeX, sizeZ 271 ))) { 272 return false; 273 } 274 } 275 276 if (schematic != null && !schematic.isEmpty()) { 277 if (!plotarea.hasSchematic(schematic)) { 278 player.sendMessage( 279 TranslatableCaption.of("schematics.schematic_invalid_named"), 280 TagResolver.builder() 281 .tag("schemname", Tag.inserting(Component.text(schematic))) 282 .tag("reason", Tag.inserting(Component.text("non-existent"))) 283 .build() 284 ); 285 return true; 286 } 287 if (!force && !player.hasPermission( 288 Permission.PERMISSION_CLAIM_SCHEMATIC.format(schematic) 289 ) && !player.hasPermission("plots.admin.command.schematic")) { 290 player.sendMessage( 291 TranslatableCaption.of("permission.no_permission"), 292 TagResolver.resolver("node", Tag.inserting(Component.text("plots.claim.%s0"))) 293 ); 294 return true; 295 } 296 } 297 if (this.econHandler != null && plotarea.useEconomy() && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { 298 PlotExpression costExp = plotarea.getPrices().get("claim"); 299 PlotExpression mergeCostExp = plotarea.getPrices().get("merge"); 300 int size = sizeX * sizeZ; 301 double mergeCost = size <= 1 || mergeCostExp == null ? 0d : mergeCostExp.evaluate(size); 302 double cost = costExp.evaluate(Settings.Limit.GLOBAL ? 303 player.getPlotCount() : 304 player.getPlotCount(plotarea.getWorldName())); 305 cost = size * cost + mergeCost; 306 if (cost > 0d) { 307 if (!this.econHandler.isSupported()) { 308 player.sendMessage(TranslatableCaption.of("economy.vault_or_consumer_null")); 309 return false; 310 } 311 if (!force && this.econHandler.getMoney(player) < cost) { 312 player.sendMessage( 313 TranslatableCaption.of("economy.cannot_afford_plot"), 314 TagResolver.builder() 315 .tag("money", Tag.inserting(Component.text(this.econHandler.format(cost)))) 316 .tag( 317 "balance", 318 Tag.inserting(Component.text(this.econHandler.format(this.econHandler.getMoney(player)))) 319 ) 320 .build() 321 ); 322 return false; 323 } 324 this.econHandler.withdrawMoney(player, cost); 325 player.sendMessage( 326 TranslatableCaption.of("economy.removed_balance"), 327 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(cost)))) 328 ); 329 } 330 } 331 332 List<Plot> plots = this.servicePipeline 333 .pump(new AutoQuery(player, null, sizeX, sizeZ, plotarea)) 334 .through(AutoService.class) 335 .getResult(); 336 337 plots = this.eventDispatcher.callAutoPlotsChosen(player, plots).getPlots(); 338 339 if (plots.isEmpty()) { 340 player.sendMessage(TranslatableCaption.of("errors.no_free_plots")); 341 return false; 342 } else if (plots.size() == 1) { 343 this.claimSingle(player, plots.get(0), plotarea, schematic); 344 } else { 345 final Iterator<Plot> plotIterator = plots.iterator(); 346 while (plotIterator.hasNext()) { 347 Plot plot = plotIterator.next(); 348 if (!plot.canClaim(player)) { 349 continue; 350 } 351 plot.claim(player, !plotIterator.hasNext(), null, true, true); 352 eventDispatcher.callPostAuto(player, plot); 353 } 354 final PlotAutoMergeEvent mergeEvent = this.eventDispatcher.callAutoMerge( 355 plots.get(0), 356 plots.stream().map(Plot::getId).collect(Collectors.toList()) 357 ); 358 if (!force && mergeEvent.getEventResult() == Result.DENY) { 359 player.sendMessage( 360 TranslatableCaption.of("events.event_denied"), 361 TagResolver.resolver("value", Tag.inserting(Component.text("Auto merge"))) 362 ); 363 return false; 364 } 365 return plotarea.mergePlots(mergeEvent.getPlots(), true); 366 } 367 return true; 368 } 369 370}