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 * Plot (X,Y) tuples for plot locations 030 * within a plot area 031 */ 032public final class PlotId { 033 034 private final int x; 035 private final int y; 036 private final int hash; 037 038 /** 039 * PlotId class (PlotId x,y values do not correspond to Block locations) 040 * 041 * @param x The plot x coordinate 042 * @param y The plot y coordinate 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 * Create a new plot ID instance 052 * 053 * @param x The plot x coordinate 054 * @param y The plot y coordinate 055 * @return a new PlotId at x,y 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 * Attempt to parse a plot ID from a string 078 * 079 * @param string ID string 080 * @return Plot ID, or {@code null} if none could be parsed 081 */ 082 public static @Nullable PlotId fromStringOrNull(final @NonNull String string) { 083 final String[] parts = string.split("[;_,.]"); 084 if (parts.length < 2) { 085 return null; 086 } 087 int x; 088 int y; 089 try { 090 x = Integer.parseInt(parts[0]); 091 y = Integer.parseInt(parts[1]); 092 } catch (final NumberFormatException ignored) { 093 return null; 094 } 095 return of(x, y); 096 } 097 098 /** 099 * Gets the PlotId from the HashCode<br> 100 * Note: Only accurate for small x,z values (short) 101 * 102 * @param hash ID hash 103 * @return Plot ID 104 */ 105 public static @NonNull PlotId unpair(final int hash) { 106 return PlotId.of(hash >> 16, hash & 0xFFFF); 107 } 108 109 /** 110 * Get the ID X component 111 * 112 * @return X component 113 */ 114 public int getX() { 115 return this.x; 116 } 117 118 /** 119 * Get the ID Y component 120 * 121 * @return Y component 122 */ 123 public int getY() { 124 return this.y; 125 } 126 127 /** 128 * Get the next plot ID for claiming purposes 129 * 130 * @return Next plot ID 131 */ 132 public @NonNull PlotId getNextId() { 133 final int absX = Math.abs(x); 134 final int absY = Math.abs(y); 135 if (absX > absY) { 136 if (x > 0) { 137 return PlotId.of(x, y + 1); 138 } else { 139 return PlotId.of(x, y - 1); 140 } 141 } else if (absY > absX) { 142 if (y > 0) { 143 return PlotId.of(x - 1, y); 144 } else { 145 return PlotId.of(x + 1, y); 146 } 147 } else { 148 if (x == y && x > 0) { 149 return PlotId.of(x, y + 1); 150 } 151 if (x == absX) { 152 return PlotId.of(x, y + 1); 153 } 154 if (y == absY) { 155 return PlotId.of(x, y - 1); 156 } 157 return PlotId.of(x + 1, y); 158 } 159 } 160 161 /** 162 * Get the PlotId in a relative direction 163 * 164 * @param direction Direction 165 * @return Relative plot ID 166 */ 167 public @NonNull PlotId getRelative(final @NonNull Direction direction) { 168 return switch (direction) { 169 case NORTH -> PlotId.of(this.getX(), this.getY() - 1); 170 case EAST -> PlotId.of(this.getX() + 1, this.getY()); 171 case SOUTH -> PlotId.of(this.getX(), this.getY() + 1); 172 case WEST -> PlotId.of(this.getX() - 1, this.getY()); 173 default -> this; 174 }; 175 } 176 177 @Override 178 public boolean equals(final Object obj) { 179 if (this == obj) { 180 return true; 181 } 182 if (obj == null) { 183 return false; 184 } 185 if (this.hashCode() != obj.hashCode()) { 186 return false; 187 } 188 if (getClass() != obj.getClass()) { 189 return false; 190 } 191 final PlotId other = (PlotId) obj; 192 return this.getX() == other.getX() && this.getY() == other.getY(); 193 } 194 195 /** 196 * Get a String representation of the plot ID where the 197 * components are separated by ";" 198 * 199 * @return {@code x + ";" + y} 200 */ 201 @Override 202 public @NonNull String toString() { 203 return this.getX() + ";" + this.getY(); 204 } 205 206 /** 207 * Get a String representation of the plot ID where the 208 * components are separated by a specified string 209 * 210 * @param separator Separator 211 * @return {@code x + separator + y} 212 */ 213 public @NonNull String toSeparatedString(String separator) { 214 return this.getX() + separator + this.getY(); 215 } 216 217 /** 218 * Get a String representation of the plot ID where the 219 * components are separated by "," 220 * 221 * @return {@code x + "," + y} 222 */ 223 public @NonNull String toCommaSeparatedString() { 224 return this.getX() + "," + this.getY(); 225 } 226 227 /** 228 * Get a String representation of the plot ID where the 229 * components are separated by "_" 230 * 231 * @return {@code x + "_" + y} 232 */ 233 public @NonNull String toUnderscoreSeparatedString() { 234 return this.getX() + "_" + this.getY(); 235 } 236 237 /** 238 * Get a String representation of the plot ID where the 239 * components are separated by "-" 240 * 241 * @return {@code x + "-" + y} 242 */ 243 public @NonNull String toDashSeparatedString() { 244 return this.getX() + "-" + this.getY(); 245 } 246 247 @Override 248 public int hashCode() { 249 return this.hash; 250 } 251 252 253 public static final class PlotRangeIterator implements Iterator<PlotId>, Iterable<PlotId> { 254 255 private final PlotId start; 256 private final PlotId end; 257 258 private int x; 259 private int y; 260 261 private PlotRangeIterator(final @NonNull PlotId start, final @NonNull PlotId end) { 262 this.start = start; 263 this.end = end; 264 this.x = this.start.getX(); 265 this.y = this.start.getY(); 266 } 267 268 public static PlotRangeIterator range(final @NonNull PlotId start, final @NonNull PlotId end) { 269 return new PlotRangeIterator(start, end); 270 } 271 272 @Override 273 public boolean hasNext() { 274 // end is fully included 275 return this.x <= this.end.getX() && this.y <= this.end.getY(); 276 } 277 278 @Override 279 public PlotId next() { 280 if (!hasNext()) { 281 throw new NoSuchElementException("The iterator has no more entries"); 282 } 283 // increment *after* getting the result to include the minimum 284 // the id to return 285 PlotId result = PlotId.of(this.x, this.y); 286 // first increase y, then x 287 if (this.y == this.end.getY()) { 288 this.x++; 289 this.y = this.start.getY(); 290 } else { 291 this.y++; 292 } 293 return result; 294 } 295 296 @NonNull 297 @Override 298 public Iterator<PlotId> iterator() { 299 return this; 300 } 301 302 } 303 304}