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}