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