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