001 package org.bukkit; 002 003 import java.util.List; 004 import java.util.Map; 005 006 import org.apache.commons.lang.Validate; 007 import org.bukkit.configuration.serialization.ConfigurationSerializable; 008 import org.bukkit.configuration.serialization.SerializableAs; 009 010 import com.google.common.collect.ImmutableList; 011 import com.google.common.collect.ImmutableMap; 012 013 /** 014 * Represents a single firework effect. 015 */ 016 @SerializableAs("Firework") 017 public final class FireworkEffect implements ConfigurationSerializable { 018 019 /** 020 * The type or shape of the effect. 021 */ 022 public enum Type { 023 /** 024 * A small ball effect. 025 */ 026 BALL, 027 /** 028 * A large ball effect. 029 */ 030 BALL_LARGE, 031 /** 032 * A star-shaped effect. 033 */ 034 STAR, 035 /** 036 * A burst effect. 037 */ 038 BURST, 039 /** 040 * A creeper-face effect. 041 */ 042 CREEPER, 043 ; 044 } 045 046 /** 047 * Construct a firework effect. 048 * 049 * @return A utility object for building a firework effect 050 */ 051 public static Builder builder() { 052 return new Builder(); 053 } 054 055 /** 056 * This is a builder for FireworkEffects. 057 * 058 * @see FireworkEffect#builder() 059 */ 060 public static final class Builder { 061 boolean flicker = false; 062 boolean trail = false; 063 final ImmutableList.Builder<Color> colors = ImmutableList.builder(); 064 ImmutableList.Builder<Color> fadeColors = null; 065 Type type = Type.BALL; 066 067 Builder() {} 068 069 /** 070 * Specify the type of the firework effect. 071 * 072 * @param type The effect type 073 * @return This object, for chaining 074 * @throws IllegalArgumentException If type is null 075 */ 076 public Builder with(Type type) throws IllegalArgumentException { 077 Validate.notNull(type, "Cannot have null type"); 078 this.type = type; 079 return this; 080 } 081 082 /** 083 * Add a flicker to the firework effect. 084 * 085 * @return This object, for chaining 086 */ 087 public Builder withFlicker() { 088 flicker = true; 089 return this; 090 } 091 092 /** 093 * Set whether the firework effect should flicker. 094 * 095 * @param flicker true if it should flicker, false if not 096 * @return This object, for chaining 097 */ 098 public Builder flicker(boolean flicker) { 099 this.flicker = flicker; 100 return this; 101 } 102 103 /** 104 * Add a trail to the firework effect. 105 * 106 * @return This object, for chaining 107 */ 108 public Builder withTrail() { 109 trail = true; 110 return this; 111 } 112 113 /** 114 * Set whether the firework effect should have a trail. 115 * 116 * @param trail true if it should have a trail, false for no trail 117 * @return This object, for chaining 118 */ 119 public Builder trail(boolean trail) { 120 this.trail = trail; 121 return this; 122 } 123 124 /** 125 * Add a primary color to the firework effect. 126 * 127 * @param color The color to add 128 * @return This object, for chaining 129 * @throws IllegalArgumentException If color is null 130 */ 131 public Builder withColor(Color color) throws IllegalArgumentException { 132 Validate.notNull(color, "Cannot have null color"); 133 134 colors.add(color); 135 136 return this; 137 } 138 139 /** 140 * Add several primary colors to the firework effect. 141 * 142 * @param colors The colors to add 143 * @return This object, for chaining 144 * @throws IllegalArgumentException If colors is null 145 * @throws IllegalArgumentException If any color is null (may be 146 * thrown after changes have occurred) 147 */ 148 public Builder withColor(Color...colors) throws IllegalArgumentException { 149 Validate.notNull(colors, "Cannot have null colors"); 150 if (colors.length == 0) { 151 return this; 152 } 153 154 ImmutableList.Builder<Color> list = this.colors; 155 for (Color color : colors) { 156 Validate.notNull(color, "Color cannot be null"); 157 list.add(color); 158 } 159 160 return this; 161 } 162 163 /** 164 * Add several primary colors to the firework effect. 165 * 166 * @param colors An iterable object whose iterator yields the desired 167 * colors 168 * @return This object, for chaining 169 * @throws IllegalArgumentException If colors is null 170 * @throws IllegalArgumentException If any color is null (may be 171 * thrown after changes have occurred) 172 */ 173 public Builder withColor(Iterable<?> colors) throws IllegalArgumentException { 174 Validate.notNull(colors, "Cannot have null colors"); 175 176 ImmutableList.Builder<Color> list = this.colors; 177 for (Object color : colors) { 178 if (!(color instanceof Color)) { 179 throw new IllegalArgumentException(color + " is not a Color in " + colors); 180 } 181 list.add((Color) color); 182 } 183 184 return this; 185 } 186 187 /** 188 * Add a fade color to the firework effect. 189 * 190 * @param color The color to add 191 * @return This object, for chaining 192 * @throws IllegalArgumentException If colors is null 193 * @throws IllegalArgumentException If any color is null (may be 194 * thrown after changes have occurred) 195 */ 196 public Builder withFade(Color color) throws IllegalArgumentException { 197 Validate.notNull(color, "Cannot have null color"); 198 199 if (fadeColors == null) { 200 fadeColors = ImmutableList.builder(); 201 } 202 203 fadeColors.add(color); 204 205 return this; 206 } 207 208 /** 209 * Add several fade colors to the firework effect. 210 * 211 * @param colors The colors to add 212 * @return This object, for chaining 213 * @throws IllegalArgumentException If colors is null 214 * @throws IllegalArgumentException If any color is null (may be 215 * thrown after changes have occurred) 216 */ 217 public Builder withFade(Color...colors) throws IllegalArgumentException { 218 Validate.notNull(colors, "Cannot have null colors"); 219 if (colors.length == 0) { 220 return this; 221 } 222 223 ImmutableList.Builder<Color> list = this.fadeColors; 224 if (list == null) { 225 list = this.fadeColors = ImmutableList.builder(); 226 } 227 228 for (Color color : colors) { 229 Validate.notNull(color, "Color cannot be null"); 230 list.add(color); 231 } 232 233 return this; 234 } 235 236 /** 237 * Add several fade colors to the firework effect. 238 * 239 * @param colors An iterable object whose iterator yields the desired 240 * colors 241 * @return This object, for chaining 242 * @throws IllegalArgumentException If colors is null 243 * @throws IllegalArgumentException If any color is null (may be 244 * thrown after changes have occurred) 245 */ 246 public Builder withFade(Iterable<?> colors) throws IllegalArgumentException { 247 Validate.notNull(colors, "Cannot have null colors"); 248 249 ImmutableList.Builder<Color> list = this.fadeColors; 250 if (list == null) { 251 list = this.fadeColors = ImmutableList.builder(); 252 } 253 254 for (Object color : colors) { 255 if (!(color instanceof Color)) { 256 throw new IllegalArgumentException(color + " is not a Color in " + colors); 257 } 258 list.add((Color) color); 259 } 260 261 return this; 262 } 263 264 /** 265 * Create a {@link FireworkEffect} from the current contents of this 266 * builder. 267 * <p> 268 * To successfully build, you must have specified at least one color. 269 * 270 * @return The representative firework effect 271 */ 272 public FireworkEffect build() { 273 return new FireworkEffect( 274 flicker, 275 trail, 276 colors.build(), 277 fadeColors == null ? ImmutableList.<Color>of() : fadeColors.build(), 278 type 279 ); 280 } 281 } 282 283 private static final String FLICKER = "flicker"; 284 private static final String TRAIL = "trail"; 285 private static final String COLORS = "colors"; 286 private static final String FADE_COLORS = "fade-colors"; 287 private static final String TYPE = "type"; 288 289 private final boolean flicker; 290 private final boolean trail; 291 private final ImmutableList<Color> colors; 292 private final ImmutableList<Color> fadeColors; 293 private final Type type; 294 private String string = null; 295 296 FireworkEffect(boolean flicker, boolean trail, ImmutableList<Color> colors, ImmutableList<Color> fadeColors, Type type) { 297 if (colors.isEmpty()) { 298 throw new IllegalStateException("Cannot make FireworkEffect without any color"); 299 } 300 this.flicker = flicker; 301 this.trail = trail; 302 this.colors = colors; 303 this.fadeColors = fadeColors; 304 this.type = type; 305 } 306 307 /** 308 * Get whether the firework effect flickers. 309 * 310 * @return true if it flickers, false if not 311 */ 312 public boolean hasFlicker() { 313 return flicker; 314 } 315 316 /** 317 * Get whether the firework effect has a trail. 318 * 319 * @return true if it has a trail, false if not 320 */ 321 public boolean hasTrail() { 322 return trail; 323 } 324 325 /** 326 * Get the primary colors of the firework effect. 327 * 328 * @return An immutable list of the primary colors 329 */ 330 public List<Color> getColors() { 331 return colors; 332 } 333 334 /** 335 * Get the fade colors of the firework effect. 336 * 337 * @return An immutable list of the fade colors 338 */ 339 public List<Color> getFadeColors() { 340 return fadeColors; 341 } 342 343 /** 344 * Get the type of the firework effect. 345 * 346 * @return The effect type 347 */ 348 public Type getType() { 349 return type; 350 } 351 352 /** 353 * @see ConfigurationSerializable 354 */ 355 public static ConfigurationSerializable deserialize(Map<String, Object> map) { 356 Type type = Type.valueOf((String) map.get(TYPE)); 357 if (type == null) { 358 throw new IllegalArgumentException(map.get(TYPE) + " is not a valid Type"); 359 } 360 361 return builder() 362 .flicker((Boolean) map.get(FLICKER)) 363 .trail((Boolean) map.get(TRAIL)) 364 .withColor((Iterable<?>) map.get(COLORS)) 365 .withFade((Iterable<?>) map.get(FADE_COLORS)) 366 .with(type) 367 .build(); 368 } 369 370 public Map<String, Object> serialize() { 371 return ImmutableMap.<String, Object>of( 372 FLICKER, flicker, 373 TRAIL, trail, 374 COLORS, colors, 375 FADE_COLORS, fadeColors, 376 TYPE, type.name() 377 ); 378 } 379 380 @Override 381 public String toString() { 382 final String string = this.string; 383 if (string == null) { 384 return this.string = "FireworkEffect:" + serialize(); 385 } 386 return string; 387 } 388 389 @Override 390 public int hashCode() { 391 /** 392 * TRUE and FALSE as per boolean.hashCode() 393 */ 394 final int PRIME = 31, TRUE = 1231, FALSE = 1237; 395 int hash = 1; 396 hash = hash * PRIME + (flicker ? TRUE : FALSE); 397 hash = hash * PRIME + (trail ? TRUE : FALSE); 398 hash = hash * PRIME + type.hashCode(); 399 hash = hash * PRIME + colors.hashCode(); 400 hash = hash * PRIME + fadeColors.hashCode(); 401 return hash; 402 } 403 404 @Override 405 public boolean equals(Object obj) { 406 if (this == obj) { 407 return true; 408 } 409 410 if (!(obj instanceof FireworkEffect)) { 411 return false; 412 } 413 414 FireworkEffect that = (FireworkEffect) obj; 415 return this.flicker == that.flicker 416 && this.trail == that.trail 417 && this.type == that.type 418 && this.colors.equals(that.colors) 419 && this.fadeColors.equals(that.fadeColors); 420 } 421 }