001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server.world; 019 020import com.google.common.collect.Collections2; 021import com.google.common.collect.ImmutableSet; 022import com.google.common.collect.Maps; 023import com.google.common.collect.Sets; 024import com.google.common.io.ByteStreams; 025import net.tridentsdk.base.Block; 026import net.tridentsdk.base.BoundingBox; 027import net.tridentsdk.base.Position; 028import net.tridentsdk.base.Substance; 029import net.tridentsdk.effect.particle.ParticleEffect; 030import net.tridentsdk.effect.particle.ParticleEffectType; 031import net.tridentsdk.effect.sound.SoundEffect; 032import net.tridentsdk.effect.sound.SoundEffectType; 033import net.tridentsdk.effect.visual.VisualEffect; 034import net.tridentsdk.effect.visual.VisualEffectType; 035import net.tridentsdk.entity.Entity; 036import net.tridentsdk.entity.Projectile; 037import net.tridentsdk.entity.block.SlotProperties; 038import net.tridentsdk.entity.living.Player; 039import net.tridentsdk.entity.living.ProjectileLauncher; 040import net.tridentsdk.entity.traits.EntityProperties; 041import net.tridentsdk.entity.types.EntityType; 042import net.tridentsdk.entity.types.HorseType; 043import net.tridentsdk.entity.types.VillagerCareer; 044import net.tridentsdk.entity.types.VillagerProfession; 045import net.tridentsdk.event.weather.RainEvent; 046import net.tridentsdk.event.weather.SunEvent; 047import net.tridentsdk.event.weather.ThunderEvent; 048import net.tridentsdk.inventory.Item; 049import net.tridentsdk.meta.block.Tile; 050import net.tridentsdk.meta.nbt.*; 051import net.tridentsdk.server.chunk.ChunkHandler; 052import net.tridentsdk.server.concurrent.ThreadsHandler; 053import net.tridentsdk.server.concurrent.TickSync; 054import net.tridentsdk.server.effect.particle.TridentParticleEffect; 055import net.tridentsdk.server.effect.sound.TridentSoundEffect; 056import net.tridentsdk.server.effect.visual.TridentVisualEffect; 057import net.tridentsdk.server.entity.TridentDroppedItem; 058import net.tridentsdk.server.entity.TridentEntity; 059import net.tridentsdk.server.entity.TridentExpOrb; 060import net.tridentsdk.server.entity.TridentFirework; 061import net.tridentsdk.server.entity.block.*; 062import net.tridentsdk.server.entity.living.*; 063import net.tridentsdk.server.entity.projectile.*; 064import net.tridentsdk.server.entity.vehicle.*; 065import net.tridentsdk.server.event.EventProcessor; 066import net.tridentsdk.server.packets.play.out.PacketPlayOutSpawnGlobalEntity; 067import net.tridentsdk.server.packets.play.out.PacketPlayOutTimeUpdate; 068import net.tridentsdk.server.player.TridentPlayer; 069import net.tridentsdk.util.Pair; 070import net.tridentsdk.util.TridentLogger; 071import net.tridentsdk.world.*; 072import net.tridentsdk.world.gen.ChunkAxisAlignedBoundingBox; 073import net.tridentsdk.world.gen.GeneratorRandom; 074import net.tridentsdk.world.settings.*; 075 076import javax.annotation.concurrent.ThreadSafe; 077import java.io.*; 078import java.nio.file.Files; 079import java.nio.file.Paths; 080import java.util.*; 081import java.util.concurrent.ThreadLocalRandom; 082import java.util.concurrent.atomic.AtomicInteger; 083import java.util.concurrent.atomic.AtomicLong; 084import java.util.function.Predicate; 085import java.util.zip.GZIPInputStream; 086import java.util.zip.GZIPOutputStream; 087 088/** 089 * Represents a world on the server 090 * 091 * @author The TridentSDK Team 092 */ 093@ThreadSafe 094public class TridentWorld implements World { 095 private static final int SIZE = 1; 096 private static final int MAX_HEIGHT = 255; 097 private static final int MAX_CHUNKS = 3_750_000; // 60 million blocks 098 private static final int CHUNK_EVICTION_TIME = 20 * 60 * 5; 099 100 private final String name; 101 private final WorldLoader loader; 102 private final Position spawnPosition; 103 104 private final ChunkHandler chunkHandler = new ChunkHandler(this); 105 private final Set<Entity> entities = Sets.newConcurrentHashSet(); 106 private final Set<Tile> tiles = Sets.newConcurrentHashSet(); 107 private final Map<GameRule, GameRule.Value> gameRules = Maps.newHashMap(); 108 109 private final AtomicLong time = new AtomicLong(); 110 private final AtomicLong existed = new AtomicLong(); 111 private final AtomicInteger rainTime = new AtomicInteger(); 112 private final AtomicInteger thunderTime = new AtomicInteger(); 113 114 private volatile double borderSize; 115 private volatile long seed; // TODO prevent seeds == 0 116 private volatile GeneratorRandom random; 117 private volatile Dimension dimension; 118 private volatile Difficulty difficulty; 119 private volatile GameMode defaultGamemode; 120 private volatile LevelType type; 121 private volatile boolean difficultyLocked; 122 private volatile boolean redstoneTick; 123 private volatile boolean raining; 124 private volatile boolean thundering; 125 private volatile boolean generateStructures = true; 126 127 private final WorldSettings settings; 128 private final WorldBorder border = new WorldBorder() { 129 private volatile Pair<Integer, Integer> center = Pair.immutable(0, 0); 130 private volatile int mod = 60000000; 131 private volatile int time = 0; 132 133 @Override 134 public int size() { 135 return 0; // todo calculate size of border? 136 } 137 138 @Override // usually modified by plugins atomically relative to the server 139 public void modify(int mod, int time) { 140 this.mod = mod; 141 if (time != 0) this.time = time; 142 apply(); 143 } 144 145 @Override 146 public Pair<Integer, Integer> center() { 147 return center; 148 } 149 150 @Override 151 public void setCenter(int x, int z) { 152 center = Pair.immutable(x, z); 153 apply(); 154 } 155 156 @Override 157 public int sizeContraction() { 158 return mod; 159 } 160 161 @Override 162 public int contractionTime() { 163 return time; 164 } 165 166 private void apply() { 167 168 } 169 }; 170 private final WeatherConditions conditions = new WeatherConditions() { 171 @Override 172 public boolean isRaining() { 173 return raining; 174 } 175 176 @Override 177 public void setRaining(boolean rain) { 178 if (rain) { 179 if (!raining) { 180 toggleRain(0); 181 } 182 } else { 183 if (raining) { 184 toggleRain(0); 185 } 186 } 187 } 188 189 @Override 190 public int rainTime() { 191 return rainTime.get(); 192 } 193 194 @Override 195 public void toggleRain(int ticks) { 196 rainTime.set(ticks); 197 } 198 199 @Override 200 public boolean isThundering() { 201 return thundering; 202 } 203 204 @Override 205 public void setThundering(boolean thunder) { 206 if (thunder) { 207 if (!thundering) { 208 toggleThunder(0); 209 } 210 } else { 211 if (thundering) { 212 toggleThunder(0); 213 } 214 } 215 } 216 217 @Override 218 public int thunderTime() { 219 return thunderTime.get(); 220 } 221 222 @Override 223 public void toggleThunder(int ticks) { 224 thunderTime.set(ticks); 225 } 226 227 @Override 228 public boolean isSunny() { 229 return !raining && !thundering; 230 } 231 232 @Override 233 public void setSunny() { 234 setRaining(false); 235 setThundering(false); 236 } 237 }; 238 239 private TridentWorld(String name, WorldLoader loader, boolean throwaway) { 240 ((TridentWorldLoader) loader).world = this; 241 this.name = name; 242 243 WorldCreateOptions options = loader.options(); 244 this.seed = options.seed(); 245 this.random = new GeneratorRandom(seed); 246 this.loader = loader; 247 this.spawnPosition = Position.create(this, 0, 0, 0); 248 249 this.dimension = options.dimension(); 250 this.difficulty = options.difficulty(); 251 this.defaultGamemode = options.defaultGameMode(); 252 // level 253 this.gameRules.clear(); 254 this.gameRules.putAll(options.gameRules()); 255 this.generateStructures = options.generateStructures(); 256 this.settings = TridentWorldSettings.load(this, options); 257 } 258 259 TridentWorld(String name, WorldLoader loader) { 260 ((TridentWorldLoader) loader).world = this; 261 this.name = name; 262 this.loader = loader; 263 this.spawnPosition = Position.create(this, 0, 0, 0); 264 265 TridentLogger.get().log("Starting to load " + name + "..."); 266 267 File directory = new File(name + File.separator); 268 File levelFile = new File(directory, "level.dat"); 269 270 InputStream fis = null; 271 try { 272 fis = new FileInputStream(levelFile); 273 274 byte[] compressedData = new byte[fis.available()]; 275 fis.read(compressedData); 276 277 CompoundTag level = new NBTDecoder(new DataInputStream(new ByteArrayInputStream( 278 ByteStreams.toByteArray(new GZIPInputStream(new ByteArrayInputStream(compressedData)))))).decode() 279 .getTagAs("Data"); 280 281 TridentLogger.get().log("Loading values of level.dat...."); 282 spawnPosition.setX(((IntTag) level.getTag("SpawnX")).value()); 283 spawnPosition.setY(((IntTag) level.getTag("SpawnY")).value() + 5); 284 spawnPosition.setZ(((IntTag) level.getTag("SpawnZ")).value()); 285 286 dimension = Dimension.OVERWORLD; 287 // difficulty = Difficulty.of(((IntTag) level.getTag("Difficulty")).value()); from tests does 288 // not exist 289 difficulty = Difficulty.NORMAL; 290 defaultGamemode = GameMode.of(((IntTag) level.getTag("GameType")).value()); 291 type = LevelType.of(((StringTag) level.getTag("generatorName")).value()); 292 seed = ((LongTag) level.getTag("RandomSeed")).value(); 293 ((TridentWorldLoader) loader).setGenerator(seed); 294 random = new GeneratorRandom(seed); 295 296 borderSize = level.containsTag("BorderSize") ? 297 ((DoubleTag) level.getTag("BorderSize")).value() : 6000; 298 299 time.set(((LongTag) level.getTag("DayTime")).value()); 300 existed.set(((LongTag) level.getTag("Time")).value()); 301 raining = ((ByteTag) level.getTag("raining")).value() == 1; 302 rainTime.set(((IntTag) level.getTag("rainTime")).value()); 303 thundering = ((ByteTag) level.getTag("thundering")).value() == 1; 304 thunderTime.set(((IntTag) level.getTag("thunderTime")).value()); 305 difficultyLocked = level.containsTag("DifficultyLocked") && 306 ((ByteTag) level.getTag("DifficultyLocked")).value() == 1; 307 308 WorldCreateOptions options = loader.options(); 309 options.dimension(dimension) 310 .difficulty(difficulty) 311 .gameMode(defaultGamemode) 312 .level(type) 313 .generator(null) // todo 314 .structures(generateStructures) 315 .pvp(true) // todo 316 .seed(String.valueOf(seed)); 317 318 gameRules.forEach(options::rule); 319 320 TridentLogger.get().success("Loaded level.dat successfully. Moving on to region files..."); 321 } catch (FileNotFoundException ignored) { 322 TridentLogger.get().error(new IllegalArgumentException("Could not find world " + name)); 323 return; 324 } catch (Exception ex) { 325 TridentLogger.get().error("Unable to load level.dat! Printing stacktrace..."); 326 TridentLogger.get().error(ex); 327 return; 328 } finally { 329 settings = TridentWorldSettings.load(this, loader.options()); 330 try { 331 if (fis != null) { 332 fis.close(); 333 } 334 } catch (IOException e) { 335 e.printStackTrace(); 336 } 337 } 338 339 // TODO: load other values 340 341 File region = new File(directory, "region" + File.separator); 342 343 if (!(region.exists()) || !(region.isDirectory())) { 344 TridentLogger.get().error( 345 new IllegalStateException("Region folder is rather non-existent or isn't a directory!")); 346 return; 347 } 348 349 TridentLogger.get().success("Loaded region files successfully. Moving onto player data..."); 350 351 TridentLogger.get().log("Loading spawn chunks..."); 352 353 int centX = ((int) Math.floor(spawnPosition.x())) >> 4; 354 int centZ = ((int) Math.floor(spawnPosition.z())) >> 4; 355 356 for (ChunkLocation location : 357 new ChunkAxisAlignedBoundingBox(ChunkLocation.create(centX - 3, centZ - 3), 358 ChunkLocation.create(centX + 3, centZ + 3))) { 359 chunkAt(location, true); 360 } 361 362 TridentLogger.get().success("Loaded spawn chunks. "); 363 364 File playerData = new File(directory, "playerdata"); 365 366 if (!(playerData.exists()) || !(playerData.isDirectory())) { 367 TridentLogger.get().warn("Player data folder does not exist. Creating folder..."); 368 playerData.mkdir(); 369 } 370 } 371 372 static TridentWorld createWorld(String name, WorldLoader loader) { 373 TridentWorld world = null; 374 375 try { 376 TridentLogger.get().log("Starting to create " + name + "..."); 377 378 TridentLogger.get().log("Creating directories and setting values..."); 379 File directory = new File(name + File.separator); 380 File levelFile = new File(directory, "level.dat"); 381 File region = new File(directory, "region" + File.separator); 382 File playerData = new File(directory, "playerdata"); 383 directory.mkdir(); 384 levelFile.createNewFile(); 385 region.mkdir(); 386 playerData.mkdir(); 387 388 world = new TridentWorld(name, loader, false); 389 world.dimension = Dimension.OVERWORLD; 390 // difficulty = Difficulty.of(((IntTag) level.getTag("Difficulty")).value()); 391 // from tests does not exist 392 world.difficulty = Difficulty.NORMAL; 393 world.defaultGamemode = GameMode.SURVIVAL; 394 world.type = LevelType.DEFAULT; 395 world.borderSize = 60000000; 396 world.time.set(0); 397 world.existed.set(0); 398 world.raining = false; 399 world.rainTime.set(0); 400 world.thundering = false; 401 world.thunderTime.set(0); 402 world.difficultyLocked = false; 403 TridentLogger.get().success("Created directories and set all values"); 404 405 // TODO: load other values 406 TridentLogger.get().log("Loading spawn chunks..."); 407 int centX = ((int) Math.floor(world.spawnPosition.x())) >> 4; 408 int centZ = ((int) Math.floor(world.spawnPosition.z())) >> 4; 409 410 ((TridentWorldLoader) loader).setGenerator(loader.options().seed()); 411 412 for (ChunkLocation location : 413 new ChunkAxisAlignedBoundingBox(ChunkLocation.create(centX - 3, centZ - 3), 414 ChunkLocation.create(centX + 3, centZ + 3))) { 415 world.chunkAt(location, true); 416 } 417 418 world.spawnPosition.setX(0); 419 world.spawnPosition.setZ(0); 420 int y = ((TridentChunk) world.spawnPosition.chunk()).maxHeightAt(0, 0); 421 world.spawnPosition().setY(y + 3); 422 423 world.save(); 424 425 TridentLogger.get().success("Loaded spawn chunks."); 426 } catch (IOException e) { 427 TridentLogger.get().error(e); 428 } 429 430 return world; 431 } 432 433 public void tick() { 434 ThreadsHandler.worldExecutor().execute(() -> { 435 redstoneTick = !redstoneTick; 436 437 long currentTime = time.get(); 438 439 rainTime.getAndDecrement(); 440 thunderTime.getAndDecrement(); 441 442 if (rainTime.get() <= 0) { 443 raining = !raining; 444 if (raining) { 445 RainEvent e = EventProcessor.fire(new RainEvent(this)); 446 if (e.isIgnored()) { 447 raining = false; 448 } 449 } else { 450 SunEvent event = EventProcessor.fire(new SunEvent(this)); 451 if (event.isIgnored()) { 452 raining = true; 453 } 454 } 455 456 rainTime.set(ThreadLocalRandom.current().nextInt()); 457 } 458 459 if (thunderTime.get() <= 0) { 460 thundering = !thundering; 461 if (thundering) { 462 ThunderEvent e = EventProcessor.fire(new ThunderEvent(this)); 463 if (e.isIgnored()) { 464 thundering = false; 465 } 466 } else { 467 // TODO do we really want this? 468 SunEvent event = EventProcessor.fire(new SunEvent(this)); 469 if (event.isIgnored()) { 470 thundering = true; 471 } 472 } 473 474 thunderTime.set(ThreadLocalRandom.current().nextInt()); 475 } 476 477 boolean updateTime = (currentTime & 40) == 0; 478 479 for (Entity entity : entities) { 480 TickSync.increment("ENTITY: uuid-" + entity.uniqueId().toString() + " id-" + entity.entityId() + " type-" + entity.type()); 481 ((TridentEntity) entity).tick(); 482 if (entity instanceof Player) { 483 TridentPlayer player = (TridentPlayer) entity; 484 485 if (updateTime) { 486 player.connection().sendPacket(new PacketPlayOutTimeUpdate().set("worldAge", existed.get()).set("time", currentTime)); 487 } 488 } 489 } 490 491 /* if ((existed.get() & CHUNK_EVICTION_TIME) == 0) { 492 UnmodifiableIterator<List<ChunkLocation>> list = Iterators.partition(Sets.newHashSet(chunkHandler.keys()).iterator(), 493 Math.max(TridentPlayer.players().size(), 1)); 494 for (; list.hasNext(); ) { 495 List<ChunkLocation> chunks = list.next(); 496 ThreadsHandler.chunkExecutor().execute(() -> chunks.forEach(chunkHandler::tryRemove)); 497 } 498 } */ 499 500 if (currentTime >= 24000) 501 time.set(0); 502 else time.getAndIncrement(); 503 existed.getAndIncrement(); 504 TickSync.complete("WORLD: " + name()); 505 }); 506 } 507 508 protected void addChunkAt(ChunkLocation location, Chunk chunk) { 509 if (location == null) { 510 TridentLogger.get().error(new NullPointerException("Location cannot be null")); 511 } 512 513 this.chunkHandler.put((TridentChunk) chunk); 514 } 515 516 public GeneratorRandom random() { 517 return random; 518 } 519 520 public void save() { 521 CompoundTag tag = new CompoundTag("Data"); 522 523 TridentLogger.get().log("Saving " + name + "..."); 524 TridentLogger.get().log("Attempting to save level data..."); 525 526 tag.addTag(new IntTag("SpawnX").setValue((int) spawnPosition.x())); 527 tag.addTag(new IntTag("SpawnY").setValue((int) spawnPosition.y())); 528 tag.addTag(new IntTag("SpawnZ").setValue((int) spawnPosition.z())); 529 tag.addTag(new DoubleTag("BorderSize").setValue(borderSize)); 530 531 tag.addTag(new ByteTag("Difficulty").setValue(difficulty.asByte())); 532 tag.addTag(new ByteTag("DifficultyLocked").setValue(difficultyLocked ? (byte) 1 : (byte) 0)); 533 tag.addTag(new LongTag("DayTime").setValue(time.get())); 534 tag.addTag(new LongTag("Time").setValue(existed.get())); 535 tag.addTag(new ByteTag("raining").setValue(raining ? (byte) 1 : (byte) 0)); 536 tag.addTag(new IntTag("GameType").setValue(defaultGamemode.asByte())); 537 tag.addTag(new StringTag("generatorName").setValue(type.toString())); 538 tag.addTag(new LongTag("RandomSeed").setValue(seed)); 539 540 tag.addTag(new IntTag("rainTime").setValue(rainTime.get())); 541 tag.addTag(new ByteTag("thundering").setValue(thundering ? (byte) 1 : (byte) 0)); 542 tag.addTag(new IntTag("thunderTime").setValue(thunderTime.get())); 543 544 // TODO add other level data 545 546 ByteArrayOutputStream os = new ByteArrayOutputStream(); 547 548 try { 549 GZIPOutputStream gzip = new GZIPOutputStream(os); 550 CompoundTag root = new CompoundTag("root"); 551 552 root.addTag(tag); 553 554 new NBTEncoder(new DataOutputStream(gzip)).encode(root); 555 gzip.close(); 556 557 Files.write(Paths.get(name, File.separator, "level.dat"), os.toByteArray()); 558 } catch (IOException | NBTException ex) { 559 TridentLogger.get().warn("Failed to save level data... printing stacktrace"); 560 TridentLogger.get().error(ex); 561 } 562 563 for (TridentChunk chunk : loadedChunks()) { 564 RegionFile.fromPath(name, chunk.location()).saveChunkData(chunk); 565 // System.out.println("saved " + chunk.x() + ":" + chunk.z()); 566 } 567 568 TridentLogger.get().log("Saved " + name + " successfully!"); 569 } 570 571 private Entity internalSpawn(Entity entity) { 572 ((TridentEntity) entity).spawn(); 573 return addEntity(entity); 574 } 575 576 public Entity addEntity(Entity entity) { 577 this.entities.add(entity); 578 return entity; 579 } 580 581 public void removeEntity(Entity entity) { 582 this.entities.remove(entity); 583 584 TridentChunk c = (TridentChunk) entity.position().chunk(); 585 if (!c.entitiesInternal().remove(entity)) { 586 for (Chunk chunk : chunkHandler.values()) { 587 // If we don't do this a simple concurrency miss 588 // can lead to a memory leak 589 if (((TridentChunk) chunk).entitiesInternal().remove(entity)) return; 590 } 591 592 throw new IllegalStateException("Entity " + entity.entityId() + 593 " type " + entity.type() + 594 " could not be removed from " + entity.position()); 595 } 596 } 597 598 @Override 599 public String name() { 600 return this.name; 601 } 602 603 @Override 604 public Collection<Chunk> chunks() { 605 return Collections2.transform(chunkHandler.values(), c -> (Chunk) c); 606 } 607 608 public Collection<TridentChunk> loadedChunks() { 609 return chunkHandler.values(); 610 } 611 612 public ChunkHandler chunkHandler() { 613 return chunkHandler; 614 } 615 616 @Override 617 public Chunk chunkAt(int x, int z, boolean generateIfNotFound) { 618 return this.chunkAt(ChunkLocation.create(x, z), generateIfNotFound); 619 } 620 621 @Override 622 public TridentChunk chunkAt(ChunkLocation location, boolean generateIfNotFound) { 623 if (location == null) { 624 return null; 625 } 626 627 return this.chunkHandler.get(location, generateIfNotFound); 628 } 629 630 @Override 631 public Chunk generateChunk(int x, int z) { 632 return this.generateChunk(ChunkLocation.create(x, z)); 633 } 634 635 @Override 636 public TridentChunk generateChunk(ChunkLocation location) { 637 if (location == null) { 638 TridentLogger.get().error(new NullPointerException("Location cannot be null")); 639 return null; 640 } 641 642 int x = location.x(); 643 int z = location.z(); 644 645 if (x > MAX_CHUNKS || x < -MAX_CHUNKS) { 646 return null; 647 } 648 649 if (z > MAX_CHUNKS || z < -MAX_CHUNKS) { 650 return null; 651 } 652 653 TridentChunk tChunk = this.chunkAt(location, false); 654 655 if (tChunk == null) { 656 if (this.loader.chunkExists(x, z)) { 657 Chunk c = this.loader.loadChunk(x, z); 658 if (c != null) { 659 this.addChunkAt(location, c); 660 return (TridentChunk) c; 661 } 662 } 663 664 TridentChunk chunk = new TridentChunk(this, x, z); 665 this.addChunkAt(location, chunk); 666 chunk.generate(); 667 // DEBUG ===== 668 //TridentLogger.get().log("Generated chunk at (" + x + "," + z + ")"); 669 // ===== 670 671 return chunk; 672 } 673 674 return tChunk; 675 } 676 677 @Override 678 public boolean equals(Object obj) { 679 if (obj instanceof TridentWorld) { 680 if (((TridentWorld) obj).name().equals(this.name)) { 681 return true; 682 } 683 } 684 return false; 685 } 686 687 @Override 688 public Block blockAt(Position location) { 689 if (!location.world().name().equals(this.name())) 690 throw new IllegalArgumentException("Provided location does not have the same world!"); 691 692 int x = (int) Math.floor(location.x()); 693 int y = (int) Math.floor(location.y()); 694 int z = (int) Math.floor(location.z()); 695 696 return this.chunkAt(WorldUtils.chunkLocation(x, z), true).blockAt(x & 15, y, z & 15); 697 } 698 699 @Override 700 public WorldLoader loader() { 701 return loader; 702 } 703 704 @Override 705 public Position spawnPosition() { 706 return spawnPosition; 707 } 708 709 @Override 710 public WeatherConditions weather() { 711 return conditions; 712 } 713 714 @Override 715 public WorldSettings settings() { 716 return this.settings; 717 } 718 719 @Override 720 public WorldBorder border() { 721 return border; 722 } 723 724 @Override 725 public long time() { 726 return time.get(); 727 } 728 729 @Override 730 public Entity spawn(EntityType type, Position spawnPosition) { 731 switch (type) { 732 case NOT_IMPL: 733 throw new UnsupportedOperationException("Cannot spawn unimplemented entity"); 734 case CREEPER: 735 return internalSpawn(new TridentCreeper(UUID.randomUUID(), spawnPosition)); 736 case BAT: 737 return internalSpawn(new TridentBat(UUID.randomUUID(), spawnPosition)); 738 case BLAZE: 739 return internalSpawn(new TridentBlaze(UUID.randomUUID(), spawnPosition)); 740 case CHICKEN: 741 return internalSpawn(new TridentChicken(UUID.randomUUID(), spawnPosition)); 742 case COW: 743 return internalSpawn(new TridentCow(UUID.randomUUID(), spawnPosition)); 744 case ENDER_DRAGON: 745 return internalSpawn(new TridentEnderDragon(UUID.randomUUID(), spawnPosition)); 746 case ENDERMAN: 747 return internalSpawn(new TridentEnderDragon(UUID.randomUUID(), spawnPosition)); 748 case ENDERMITE: 749 return internalSpawn(new TridentEndermite(UUID.randomUUID(), spawnPosition)); 750 case GHAST: 751 return internalSpawn(new TridentGhast(UUID.randomUUID(), spawnPosition)); 752 case HORSE: 753 return internalSpawn(new TridentHorse(UUID.randomUUID(), spawnPosition, HorseType.HORSE)); 754 case MAGMA_CUBE: 755 return internalSpawn(new TridentMagmaCube(UUID.randomUUID(), spawnPosition)); 756 case MOOSHROOM: 757 return internalSpawn(new TridentMooshroom(UUID.randomUUID(), spawnPosition)); 758 case OCELOT: 759 return internalSpawn(new TridentOcelot(UUID.randomUUID(), spawnPosition)); 760 case PIG: 761 return internalSpawn(new TridentPig(UUID.randomUUID(), spawnPosition)); 762 case GUARDIAN: 763 return internalSpawn(new TridentGuardian(UUID.randomUUID(), spawnPosition)); 764 case PLAYER: 765 // Handle specially 766 case RABBIT: 767 return internalSpawn(new TridentRabbit(UUID.randomUUID(), spawnPosition)); 768 case SHEEP: 769 return internalSpawn(new TridentSheep(UUID.randomUUID(), spawnPosition)); 770 case SKELETON: 771 return internalSpawn(new TridentSkeleton(UUID.randomUUID(), spawnPosition)); 772 case SLIME: 773 return internalSpawn(new TridentSlime(UUID.randomUUID(), spawnPosition)); 774 case VILLAGER: 775 return internalSpawn(new TridentVillager(UUID.randomUUID(), spawnPosition, 776 VillagerCareer.FARMER, VillagerProfession.FARMER)); 777 case WITHER: 778 return internalSpawn(new TridentWither(UUID.randomUUID(), spawnPosition)); 779 case WOLF: 780 return internalSpawn(new TridentWolf(UUID.randomUUID(), spawnPosition)); 781 case ZOMBIE: 782 return internalSpawn(new TridentZombie(UUID.randomUUID(), spawnPosition)); 783 case ARMOR_STAND: 784 return internalSpawn(new TridentArmorStand(UUID.randomUUID(), spawnPosition, new SlotProperties() { 785 })); 786 case FALLING_BLOCK: 787 return internalSpawn(new TridentFallingBlock(UUID.randomUUID(), spawnPosition)); 788 case ITEM_FRAME: 789 return internalSpawn(new TridentItemFrame(UUID.randomUUID(), spawnPosition)); 790 case PAINTING: 791 return internalSpawn(new TridentPainting(UUID.randomUUID(), spawnPosition)); 792 case PRIMED_TNT: 793 return internalSpawn(new TridentPrimeTNT(UUID.randomUUID(), spawnPosition)); 794 case ARROW: 795 return internalSpawn(new TridentArrow(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 796 @Override 797 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 798 return null; 799 } 800 })); 801 case EGG: 802 return internalSpawn(new TridentEgg(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 803 @Override 804 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 805 return null; 806 } 807 })); 808 case ENDER_PEARL: 809 return internalSpawn(new TridentEnderPearl(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 810 @Override 811 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 812 return null; 813 } 814 })); 815 case EXPERIENCE_BOTTLE: 816 return internalSpawn(new TridentExpBottle(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 817 @Override 818 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 819 return null; 820 } 821 })); 822 case FIREBALL: 823 return internalSpawn(new TridentFireball(UUID.randomUUID(), spawnPosition, 824 new ProjectileLauncher() { 825 @Override 826 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 827 return null; 828 } 829 })); 830 case FISH_HOOK: 831 return internalSpawn(new TridentFishHook(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 832 @Override 833 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 834 return null; 835 } 836 })); 837 case POTION: 838 return internalSpawn(new TridentPotion(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 839 @Override 840 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 841 return null; 842 } 843 })); 844 case SMALL_FIREBALL: 845 return internalSpawn(new TridentSmallFireball(UUID.randomUUID(), spawnPosition, 846 new ProjectileLauncher() { 847 @Override 848 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 849 return null; 850 } 851 })); 852 case SNOWBALL: 853 return internalSpawn(new TridentSnowball(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 854 @Override 855 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 856 return null; 857 } 858 })); 859 case WITHER_SKULL: 860 return internalSpawn(new TridentWitherSkull(UUID.randomUUID(), spawnPosition, new ProjectileLauncher() { 861 @Override 862 public <T extends Projectile> T launchProjectile(EntityProperties properties) { 863 return null; 864 } 865 })); 866 case BOAT: 867 return internalSpawn(new TridentBoat(UUID.randomUUID(), spawnPosition)); 868 case COMMAND_MINECART: 869 return internalSpawn(new TridentCmdMinecart(UUID.randomUUID(), spawnPosition)); 870 case FURNANCE_MINECART: 871 return internalSpawn(new TridentFurnaceMinecart(UUID.randomUUID(), spawnPosition)); 872 case HOPPER_MINECART: 873 return internalSpawn(new TridentHopperMinecart(UUID.randomUUID(), spawnPosition)); 874 case MINECART: 875 return internalSpawn(new TridentMinecart(UUID.randomUUID(), spawnPosition)); 876 case SPAWNER_MINECART: 877 return internalSpawn(new TridentSpawnerMinecart(UUID.randomUUID(), spawnPosition)); 878 case TNT_MINECART: 879 return internalSpawn(new TridentTntMinecart(UUID.randomUUID(), spawnPosition)); 880 case ITEM: 881 return internalSpawn(new TridentDroppedItem(spawnPosition, new Item(Substance.STONE))); 882 case EXPERIENCE_ORB: 883 return internalSpawn(new TridentExpOrb(UUID.randomUUID(), spawnPosition)); 884 case FIREWORK: 885 return internalSpawn(new TridentFirework(UUID.randomUUID(), spawnPosition)); 886 } 887 888 // If it reaches here... that is really bad.... 889 throw new UnsupportedOperationException("Cannot spawn that type of entity"); 890 } 891 892 @Override 893 public Set<Entity> entities() { 894 return ImmutableSet.copyOf(this.entities); 895 } 896 897 public Set<Entity> internalEntities() { 898 return this.entities; 899 } 900 901 public Set<Tile> tilesInternal() { 902 return tiles; 903 } 904 905 @Override 906 public ParticleEffect spawnParticle(ParticleEffectType particle) { 907 return new TridentParticleEffect(this, particle); 908 } 909 910 @Override 911 public VisualEffect spawnVisual(VisualEffectType visual) { 912 return new TridentVisualEffect(this, visual); 913 } 914 915 @Override 916 public SoundEffect playSound(SoundEffectType sound) { 917 return new TridentSoundEffect(this, sound); 918 } 919 920 @Override 921 public void lightning(Position position, boolean b) { 922 //TODO implement lightning effect and test this 923 PacketPlayOutSpawnGlobalEntity lightningPacket = new PacketPlayOutSpawnGlobalEntity(); 924 lightningPacket.set("loc", position); 925 for (Entity entity : entities) { 926 if (entity instanceof Player) { 927 TridentPlayer player = (TridentPlayer) entity; 928 player.connection().sendPacket(lightningPacket); 929 } 930 } 931 } 932 933 @Override 934 public void setTime(long l) { 935 time.set(l); 936 } 937 938 public ArrayList<Entity> getEntities(Entity exclude, BoundingBox boundingBox, Predicate<? super Entity> predicate) { 939 ArrayList<Entity> list = new ArrayList<>(); 940 int minX = (int) Math.floor((boundingBox.minX() - 2.0D) / 16.0D); 941 int maxX = (int) Math.floor((boundingBox.maxX() + 2.0D) / 16.0D); 942 int minZ = (int) Math.floor((boundingBox.minZ() - 2.0D) / 16.0D); 943 int maxZ = (int) Math.floor((boundingBox.maxZ() + 2.0D) / 16.0D); 944 for (int x = minX; x <= maxX; x++) { 945 for (int z = minZ; z <= maxZ; z++) { 946 Chunk chunk = chunkAt(x, z, false); 947 if (chunk != null) { 948 list.addAll(chunk.getEntities(exclude, boundingBox, predicate)); 949 } 950 } 951 } 952 return list; 953 } 954 955 @Override 956 public String toString() { 957 return name + "@" + hashCode(); 958 } 959}