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.plot; 020 021import com.plotsquared.core.location.Direction; 022import org.checkerframework.checker.nullness.qual.NonNull; 023import org.checkerframework.checker.nullness.qual.Nullable; 024 025import java.util.Iterator; 026import java.util.NoSuchElementException; 027 028/** 029 * The PlotId class represents a Plot's x and y coordinates within a {@link PlotArea}. PlotId x,y values do not correspond to Block locations. 030 * A PlotId instance can be created using the {@link #of(int, int)} method or parsed from a string using the {@link #fromString(String)} method. 031 */ 032public final class PlotId { 033 034 private final int x; 035 private final int y; 036 private final int hash; 037 038 /** 039 * Constructs a new PlotId with the given x and y coordinates. 040 * 041 * @param x the x-coordinate of the plot 042 * @param y the y-coordinate of the plot 043 */ 044 private PlotId(final int x, final int y) { 045 this.x = x; 046 this.y = y; 047 this.hash = (this.getX() << 16) | (this.getY() & 0xFFFF); 048 } 049 050 /** 051 * Returns a new PlotId instance with the specified x and y coordinates. 052 * 053 * @param x the x-coordinate of the plot 054 * @param y the y-coordinate of the plot 055 * @return a new PlotId instance with the specified x and y coordinates 056 */ 057 public static @NonNull PlotId of(final int x, final int y) { 058 return new PlotId(x, y); 059 } 060 061 /** 062 * Get a Plot Id based on a string 063 * 064 * @param string to create id from 065 * @return the PlotId representation of the argument 066 * @throws IllegalArgumentException if the string does not contain a valid PlotId 067 */ 068 public static @NonNull PlotId fromString(final @NonNull String string) { 069 final PlotId plot = fromStringOrNull(string); 070 if (plot == null) { 071 throw new IllegalArgumentException("Cannot create PlotID. String invalid."); 072 } 073 return plot; 074 } 075 076 /** 077 * Returns a PlotId object from the given string, or null if the string is invalid. 078 * The string should be in the format "x;y" where x and y are integers. 079 * The string can also contain any combination of the characters ";_,." 080 * as delimiters. 081 * 082 * @param string the string to parse 083 * @return a PlotId object parsed from the given string, or null if the string is invalid 084 */ 085 public static @Nullable PlotId fromStringOrNull(final @NonNull String string) { 086 final String[] parts = string.split("[;_,.]"); 087 if (parts.length < 2) { 088 return null; 089 } 090 int x; 091 int y; 092 try { 093 x = Integer.parseInt(parts[0]); 094 y = Integer.parseInt(parts[1]); 095 } catch (final NumberFormatException ignored) { 096 return null; 097 } 098 return of(x, y); 099 } 100 101 102 /** 103 * Returns a new PlotId instance from the given hash. 104 * 105 * @param hash the hash to unpair 106 * @return a new PlotId instance 107 */ 108 public static @NonNull PlotId unpair(final int hash) { 109 return PlotId.of(hash >> 16, hash & 0xFFFF); 110 } 111 112 /** 113 * Returns the x-coordinate of this Plot ID. 114 * 115 * @return the x-coordinate of this Plot ID 116 */ 117 public int getX() { 118 return this.x; 119 } 120 121 /** 122 * Returns the y-coordinate of this Plot ID. 123 * 124 * @return the y-coordinate of this Plot ID 125 */ 126 public int getY() { 127 return this.y; 128 } 129 130 /** 131 * Returns the next Plot ID for claiming purposes based on the current Plot ID. 132 * 133 * @return the next Plot ID 134 */ 135 public @NonNull PlotId getNextId() { 136 final int absX = Math.abs(x); 137 final int absY = Math.abs(y); 138 if (absX > absY) { 139 if (x > 0) { 140 return PlotId.of(x, y + 1); 141 } else { 142 return PlotId.of(x, y - 1); 143 } 144 } else if (absY > absX) { 145 if (y > 0) { 146 return PlotId.of(x - 1, y); 147 } else { 148 return PlotId.of(x + 1, y); 149 } 150 } else { 151 if (x == y && x > 0) { 152 return PlotId.of(x, y + 1); 153 } 154 if (x == absX) { 155 return PlotId.of(x, y + 1); 156 } 157 if (y == absY) { 158 return PlotId.of(x, y - 1); 159 } 160 return PlotId.of(x + 1, y); 161 } 162 } 163 164 /** 165 * Returns a new Plot ID in the specified relative direction based on the 166 * current Plot ID. 167 * 168 * @param direction the direction in which to get the relative Plot ID 169 * @return the relative Plot ID 170 */ 171 public @NonNull PlotId getRelative(final @NonNull Direction direction) { 172 return switch (direction) { 173 case NORTH -> PlotId.of(this.getX(), this.getY() - 1); 174 case EAST -> PlotId.of(this.getX() + 1, this.getY()); 175 case SOUTH -> PlotId.of(this.getX(), this.getY() + 1); 176 case WEST -> PlotId.of(this.getX() - 1, this.getY()); 177 default -> this; 178 }; 179 } 180 181 @Override 182 public boolean equals(final Object obj) { 183 if (this == obj) { 184 return true; 185 } 186 if (obj == null) { 187 return false; 188 } 189 if (this.hashCode() != obj.hashCode()) { 190 return false; 191 } 192 if (getClass() != obj.getClass()) { 193 return false; 194 } 195 final PlotId other = (PlotId) obj; 196 return this.getX() == other.getX() && this.getY() == other.getY(); 197 } 198 199 /** 200 * Returns a string representation of this Plot ID in the format "x;y". 201 * 202 * <p> The format is {@code x + ";" + y} 203 * 204 * @return a string representation of this Plot ID 205 */ 206 @Override 207 public @NonNull String toString() { 208 return this.getX() + ";" + this.getY(); 209 } 210 211 /** 212 * Returns a string representation of this Plot ID with the specified separator. 213 * <p> 214 * The format is {@code x + separator + y} 215 * 216 * @param separator the separator to use between the X and Y coordinates 217 * @return a string representation of this Plot ID with the specified separator 218 */ 219 public @NonNull String toSeparatedString(String separator) { 220 return this.getX() + separator + this.getY(); 221 } 222 223 /** 224 * Returns a string representation of this Plot ID in the format "x,y". 225 * 226 * @return a string representation of this Plot ID 227 */ 228 public @NonNull String toCommaSeparatedString() { 229 return this.getX() + "," + this.getY(); 230 } 231 232 /** 233 * Returns a string representation of this Plot ID in the format "x_y". 234 * 235 * @return a string representation of this Plot ID 236 */ 237 238 public @NonNull String toUnderscoreSeparatedString() { 239 return this.getX() + "_" + this.getY(); 240 } 241 242 /** 243 * Returns a string representation of this Plot ID in the format "x-y". 244 * 245 * @return a string representation of this Plot ID 246 */ 247 public @NonNull String toDashSeparatedString() { 248 return this.getX() + "-" + this.getY(); 249 } 250 251 @Override 252 public int hashCode() { 253 return this.hash; 254 } 255 256 257 /** 258 * An iterator that iterates over a range of {@link PlotId}s. 259 * The range is defined by a start and end {@link PlotId}. 260 */ 261 public static final class PlotRangeIterator implements Iterator<PlotId>, Iterable<PlotId> { 262 263 private final PlotId start; 264 private final PlotId end; 265 266 private int x; 267 private int y; 268 269 private PlotRangeIterator(final @NonNull PlotId start, final @NonNull PlotId end) { 270 this.start = start; 271 this.end = end; 272 this.x = this.start.getX(); 273 this.y = this.start.getY(); 274 } 275 276 /** 277 * Returns a new {@link PlotRangeIterator} that iterates over the range of Plots between the specified start and end Plots (inclusive). 278 * 279 * @param start the starting Plot of the range 280 * @param end the ending Plot of the range 281 * @return a new {@link PlotRangeIterator} that iterates over the range of Plots between the specified start and end Plots (inclusive) 282 */ 283 public static PlotRangeIterator range(final @NonNull PlotId start, final @NonNull PlotId end) { 284 return new PlotRangeIterator(start, end); 285 } 286 287 @Override 288 public boolean hasNext() { 289 // end is fully included 290 return this.x <= this.end.getX() && this.y <= this.end.getY(); 291 } 292 293 @Override 294 public PlotId next() { 295 if (!hasNext()) { 296 throw new NoSuchElementException("The iterator has no more entries"); 297 } 298 // increment *after* getting the result to include the minimum 299 // the id to return 300 PlotId result = PlotId.of(this.x, this.y); 301 // first increase y, then x 302 if (this.y == this.end.getY()) { 303 this.x++; 304 this.y = this.start.getY(); 305 } else { 306 this.y++; 307 } 308 return result; 309 } 310 311 @NonNull 312 @Override 313 public Iterator<PlotId> iterator() { 314 return this; 315 } 316 317 } 318 319}