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.PlotSquared; 023import com.plotsquared.core.configuration.Settings; 024import com.plotsquared.core.configuration.caption.TranslatableCaption; 025import com.plotsquared.core.events.PlotMergeEvent; 026import com.plotsquared.core.events.Result; 027import com.plotsquared.core.location.Direction; 028import com.plotsquared.core.location.Location; 029import com.plotsquared.core.permissions.Permission; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.Plot; 032import com.plotsquared.core.plot.PlotArea; 033import com.plotsquared.core.util.EconHandler; 034import com.plotsquared.core.util.EventDispatcher; 035import com.plotsquared.core.util.PlotExpression; 036import com.plotsquared.core.util.StringMan; 037import net.kyori.adventure.text.Component; 038import net.kyori.adventure.text.minimessage.tag.Tag; 039import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 040import org.checkerframework.checker.nullness.qual.NonNull; 041 042import java.util.UUID; 043 044@CommandDeclaration(command = "merge", 045 aliases = "m", 046 permission = "plots.merge", 047 usage = "/plot merge <all | n | e | s | w> [removeroads]", 048 category = CommandCategory.SETTINGS, 049 requiredType = RequiredType.NONE, 050 confirmation = true) 051public class Merge extends SubCommand { 052 053 public static final String[] values = new String[]{"north", "east", "south", "west"}; 054 public static final String[] aliases = new String[]{"n", "e", "s", "w"}; 055 056 private final EventDispatcher eventDispatcher; 057 private final EconHandler econHandler; 058 059 @Inject 060 public Merge( 061 final @NonNull EventDispatcher eventDispatcher, 062 final @NonNull EconHandler econHandler 063 ) { 064 this.eventDispatcher = eventDispatcher; 065 this.econHandler = econHandler; 066 } 067 068 public static String direction(float yaw) { 069 yaw = yaw / 90; 070 int i = Math.round(yaw); 071 return switch (i) { 072 case -4, 0, 4 -> "SOUTH"; 073 case -1, 3 -> "EAST"; 074 case -2, 2 -> "NORTH"; 075 case -3, 1 -> "WEST"; 076 default -> ""; 077 }; 078 } 079 080 @Override 081 public boolean onCommand(final PlotPlayer<?> player, String[] args) { 082 Location location = player.getLocationFull(); 083 final Plot plot = location.getPlotAbs(); 084 if (plot == null) { 085 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 086 return false; 087 } 088 if (!plot.hasOwner()) { 089 player.sendMessage(TranslatableCaption.of("info.plot_unowned")); 090 return false; 091 } 092 if (plot.getVolume() > Integer.MAX_VALUE) { 093 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 094 return false; 095 } 096 Direction direction = null; 097 if (args.length == 0) { 098 switch (direction(player.getLocationFull().getYaw())) { 099 case "NORTH" -> direction = Direction.NORTH; 100 case "EAST" -> direction = Direction.EAST; 101 case "SOUTH" -> direction = Direction.SOUTH; 102 case "WEST" -> direction = Direction.WEST; 103 } 104 } else { 105 for (int i = 0; i < values.length; i++) { 106 if (args[0].equalsIgnoreCase(values[i]) || args[0].equalsIgnoreCase(aliases[i])) { 107 direction = Direction.getFromIndex(i); 108 break; 109 } 110 } 111 if (direction == null && (args[0].equalsIgnoreCase("all") || args[0] 112 .equalsIgnoreCase("auto")) && player.hasPermission(Permission.PERMISSION_MERGE_ALL)) { 113 direction = Direction.ALL; 114 } 115 } 116 if (direction == null) { 117 player.sendMessage( 118 TranslatableCaption.of("commandconfig.command_syntax"), 119 TagResolver.resolver("value", Tag.inserting(Component.text( 120 "/plot merge <" + StringMan.join(values, " | ") + "> [removeroads]" 121 ))) 122 ); 123 player.sendMessage( 124 TranslatableCaption.of("help.direction"), 125 TagResolver.resolver("dir", Tag.inserting(Component.text(direction(location.getYaw())))) 126 ); 127 return false; 128 } 129 final int size = plot.getConnectedPlots().size(); 130 int max = player.hasPermissionRange("plots.merge", Settings.Limit.MAX_PLOTS); 131 PlotMergeEvent event = 132 this.eventDispatcher.callMerge(plot, direction, max, player); 133 if (event.getEventResult() == Result.DENY) { 134 player.sendMessage( 135 TranslatableCaption.of("events.event_denied"), 136 TagResolver.resolver("value", Tag.inserting(Component.text("Merge"))) 137 ); 138 return false; 139 } 140 boolean force = event.getEventResult() == Result.FORCE; 141 direction = event.getDir(); 142 final int maxSize = event.getMax(); 143 144 if (!force && size - 1 > maxSize) { 145 player.sendMessage( 146 TranslatableCaption.of("permission.no_permission"), 147 TagResolver.resolver("node", Tag.inserting(Component.text(Permission.PERMISSION_MERGE + "." + (size + 1)))) 148 ); 149 return false; 150 } 151 final PlotArea plotArea = plot.getArea(); 152 PlotExpression priceExr = plotArea.getPrices().getOrDefault("merge", null); 153 final double price = priceExr == null ? 0d : priceExr.evaluate(size); 154 155 UUID uuid = player.getUUID(); 156 157 if (!force && !plot.isOwner(uuid)) { 158 if (!player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_MERGE)) { 159 player.sendMessage(TranslatableCaption.of("permission.no_plot_perms")); 160 return false; 161 } else { 162 uuid = plot.getOwnerAbs(); 163 } 164 } 165 if (direction == Direction.ALL) { 166 boolean terrain = true; 167 if (args.length == 2) { 168 terrain = "true".equalsIgnoreCase(args[1]); 169 } 170 if (!force && !terrain && !player.hasPermission(Permission.PERMISSION_MERGE_KEEP_ROAD)) { 171 player.sendMessage( 172 TranslatableCaption.of("permission.no_permission"), 173 TagResolver.resolver( 174 "node", 175 Tag.inserting(Permission.PERMISSION_MERGE_KEEP_ROAD) 176 ) 177 ); 178 return true; 179 } 180 if (plot.getPlotModificationManager().autoMerge(Direction.ALL, maxSize, uuid, player, terrain)) { 181 if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { 182 this.econHandler.withdrawMoney(player, price); 183 player.sendMessage( 184 TranslatableCaption.of("economy.removed_balance"), 185 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))), 186 TagResolver.resolver( 187 "balance", 188 Tag.inserting(Component.text(this.econHandler.format(this.econHandler.getMoney(player)))) 189 ) 190 ); 191 } 192 player.sendMessage(TranslatableCaption.of("merge.success_merge")); 193 eventDispatcher.callPostMerge(player, plot); 194 return true; 195 } 196 player.sendMessage(TranslatableCaption.of("merge.no_available_automerge")); 197 return false; 198 } 199 if (!force && this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d && this.econHandler.getMoney( 200 player) < price) { 201 player.sendMessage( 202 TranslatableCaption.of("economy.cannot_afford_merge"), 203 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) 204 ); 205 return false; 206 } 207 final boolean terrain; 208 if (args.length == 2) { 209 terrain = "true".equalsIgnoreCase(args[1]); 210 } else { 211 terrain = true; 212 } 213 if (!force && !terrain && !player.hasPermission(Permission.PERMISSION_MERGE_KEEP_ROAD)) { 214 player.sendMessage( 215 TranslatableCaption.of("permission.no_permission"), 216 TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_MERGE_KEEP_ROAD)) 217 ); 218 return true; 219 } 220 if (plot.getPlotModificationManager().autoMerge(direction, maxSize - size, uuid, player, terrain)) { 221 if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { 222 this.econHandler.withdrawMoney(player, price); 223 player.sendMessage( 224 TranslatableCaption.of("economy.removed_balance"), 225 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) 226 ); 227 } 228 player.sendMessage(TranslatableCaption.of("merge.success_merge")); 229 eventDispatcher.callPostMerge(player, plot); 230 return true; 231 } 232 Plot adjacent = plot.getRelative(direction); 233 if (adjacent == null || !adjacent.hasOwner() || adjacent 234 .isMerged((direction.getIndex() + 2) % 4) || (!force && adjacent.isOwner(uuid))) { 235 player.sendMessage(TranslatableCaption.of("merge.no_available_automerge")); 236 return false; 237 } 238 if (!force && !player.hasPermission(Permission.PERMISSION_MERGE_OTHER)) { 239 player.sendMessage( 240 TranslatableCaption.of("permission.no_permission"), 241 TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_MERGE_OTHER)) 242 ); 243 return false; 244 } 245 java.util.Set<UUID> uuids = adjacent.getOwners(); 246 boolean isOnline = false; 247 for (final UUID owner : uuids) { 248 final PlotPlayer<?> accepter = PlotSquared.platform().playerManager().getPlayerIfExists(owner); 249 if (!force && accepter == null) { 250 continue; 251 } 252 isOnline = true; 253 final Direction dir = direction; 254 Runnable run = () -> { 255 accepter.sendMessage(TranslatableCaption.of("merge.merge_accepted")); 256 plot.getPlotModificationManager().autoMerge(dir, maxSize - size, owner, player, terrain); 257 PlotPlayer<?> plotPlayer = PlotSquared.platform().playerManager().getPlayerIfExists(player.getUUID()); 258 if (plotPlayer == null) { 259 accepter.sendMessage(TranslatableCaption.of("merge.merge_not_valid")); 260 return; 261 } 262 if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { 263 if (!force && this.econHandler.getMoney(player) < price) { 264 player.sendMessage( 265 TranslatableCaption.of("economy.cannot_afford_merge"), 266 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) 267 ); 268 return; 269 } 270 this.econHandler.withdrawMoney(player, price); 271 player.sendMessage( 272 TranslatableCaption.of("economy.removed_balance"), 273 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) 274 ); 275 } 276 player.sendMessage(TranslatableCaption.of("merge.success_merge")); 277 eventDispatcher.callPostMerge(player, plot); 278 }; 279 if (!force && hasConfirmation(player)) { 280 CmdConfirm.addPending(accepter, MINI_MESSAGE.serialize(MINI_MESSAGE 281 .deserialize( 282 TranslatableCaption.of("merge.merge_request_confirm").getComponent(player), 283 TagResolver.builder() 284 .tag("player", Tag.inserting(Component.text(player.getName()))) 285 .tag( 286 "location", 287 Tag.inserting(Component.text(plot.getWorldName() + " " + plot.getId())) 288 ) 289 .build() 290 )), 291 run 292 ); 293 } else { 294 run.run(); 295 } 296 } 297 if (force || !isOnline) { 298 if (force || player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_MERGE_OTHER_OFFLINE)) { 299 if (plot.getPlotModificationManager().autoMerge( 300 direction, 301 maxSize - size, 302 uuids.iterator().next(), 303 player, 304 terrain 305 )) { 306 if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { 307 if (!force && this.econHandler.getMoney(player) < price) { 308 player.sendMessage( 309 TranslatableCaption.of("economy.cannot_afford_merge"), 310 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) 311 ); 312 return false; 313 } 314 this.econHandler.withdrawMoney(player, price); 315 player.sendMessage( 316 TranslatableCaption.of("economy.removed_balance"), 317 TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) 318 ); 319 } 320 player.sendMessage(TranslatableCaption.of("merge.success_merge")); 321 eventDispatcher.callPostMerge(player, plot); 322 return true; 323 } 324 } 325 player.sendMessage(TranslatableCaption.of("merge.no_available_automerge")); 326 return false; 327 } 328 player.sendMessage(TranslatableCaption.of("merge.merge_requested")); 329 return true; 330 } 331 332}