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.ImmutableSet;
021import com.google.common.collect.Lists;
022import com.google.common.collect.Maps;
023import com.google.common.collect.Sets;
024import net.tridentsdk.base.Block;
025import net.tridentsdk.base.BoundingBox;
026import net.tridentsdk.base.Position;
027import net.tridentsdk.base.Substance;
028import net.tridentsdk.entity.Entity;
029import net.tridentsdk.meta.block.BlockMeta;
030import net.tridentsdk.meta.block.Tile;
031import net.tridentsdk.meta.nbt.*;
032import net.tridentsdk.server.chunk.CRefCounter;
033import net.tridentsdk.server.chunk.ChunkHandler;
034import net.tridentsdk.server.chunk.ConcurrentSectionTable;
035import net.tridentsdk.server.entity.TridentEntity;
036import net.tridentsdk.server.packets.play.out.PacketPlayOutChunkData;
037import net.tridentsdk.util.NibbleArray;
038import net.tridentsdk.util.TridentLogger;
039import net.tridentsdk.util.Vector;
040import net.tridentsdk.world.Chunk;
041import net.tridentsdk.world.ChunkLocation;
042import net.tridentsdk.world.ChunkSnapshot;
043import net.tridentsdk.world.gen.ChunkGenerator;
044import net.tridentsdk.world.gen.FeatureGenerator;
045import net.tridentsdk.world.gen.FeatureGenerator.ChunkManipulator;
046
047import javax.annotation.concurrent.GuardedBy;
048import java.io.ByteArrayOutputStream;
049import java.io.IOException;
050import java.util.*;
051import java.util.concurrent.ConcurrentHashMap;
052import java.util.concurrent.atomic.AtomicInteger;
053import java.util.concurrent.atomic.AtomicReferenceArray;
054import java.util.function.Predicate;
055import java.util.stream.Collectors;
056
057public class TridentChunk implements Chunk {
058    private final TridentWorld world;
059    private final ChunkLocation location;
060
061    @GuardedBy("sections")
062    public final ConcurrentSectionTable sections = new ConcurrentSectionTable();
063    private final Set<Entity> entities = Sets.newConcurrentHashSet();
064    private final Map<Vector, List<BlockMeta>> blockMeta = Maps.newConcurrentMap();
065    private final AtomicReferenceArray<Integer> heights = new AtomicReferenceArray<>(256);
066
067    private volatile int lastFileAccess;
068    private volatile long lastModified;
069    private volatile long inhabitedTime;
070    private final AtomicInteger lightPopulated = new AtomicInteger();
071    private final AtomicInteger terrainPopulated = new AtomicInteger();
072
073    protected TridentChunk(TridentWorld world, int x, int z) {
074        this(world, ChunkLocation.create(x, z));
075    }
076
077    protected TridentChunk(TridentWorld world, ChunkLocation coord) {
078        this.world = world;
079        location = coord;
080        lastFileAccess = 0;
081        for (int i = 0; i < 256; i++) {
082            heights.set(i, 0);
083        }
084    }
085
086    protected int lastFileAccess() {
087        return lastFileAccess;
088    }
089
090    protected void setLastFileAccess(int last) {
091        lastFileAccess = last;
092    }
093
094    @Override
095    public boolean isLoaded() {
096        return lightPopulated.get() == 0x01 && terrainPopulated.get() == 0x01;
097    }
098
099    @Override
100    public Set<Entity> entities() {
101        return ImmutableSet.copyOf(entities);
102    }
103
104    @Override
105    public Collection<Tile> tiles() {
106        List<Tile> concat = Lists.newArrayList();
107        for (List<BlockMeta> metaList : blockMeta.values()) {
108            for (BlockMeta meta : metaList) {
109                if (meta instanceof Tile) {
110                    concat.add((Tile) meta);
111                }
112            }
113        }
114
115        return concat;
116    }
117
118    public Set<Entity> entitiesInternal() {
119        return entities;
120    }
121
122    public Map<Vector, List<BlockMeta>> tilesInternal() {
123        return blockMeta;
124    }
125
126    public void gen(boolean withPaint) {
127        // Has or is generated already if the state is not 0x00
128        if (!lightPopulated.compareAndSet(0x00, 0xFFFFFFFF)) {
129            if (withPaint) paint(true);
130            return;
131        }
132
133        sections.lockFully();
134        try {
135            ChunkGenerator generator = world.loader().generator();
136            char[][] blocks = generator.generateBlocks(location, heights);
137            byte[][] data = generator.generateData(location);
138            for (int i = 0; i < 16; i++) {
139                ChunkSection section = sections.get(i);
140
141                if (blocks != null && blocks.length > 0) {
142                    char[] sector = blocks[i];
143                    if (sector != null && sector.length > 0) {
144                        section.setBlocks(sector);
145                    }
146                }
147
148                if (data != null && data.length > 0) {
149                    byte[] sector = data[i];
150                    if (sector != null && sector.length > 0) {
151                        section.setData(sector);
152                    }
153                }
154
155                // DEBUG ===== makes the entire chunk completely lit, not ideal for production
156                Arrays.fill(section.skyLight, (byte) 255);
157                // =====
158                section.updateRaw();
159            }
160
161            if (withPaint) {
162                paint(false);
163            }
164        } finally {
165            sections.release();
166        }
167
168        lightPopulated.set(0x01);
169        //TODO lighting
170    }
171
172    @Override
173    public void generate() {
174        gen(true);
175    }
176
177    @Override
178    public boolean load() {
179        if (isLoaded()) {
180            return false;
181        }
182
183        CompoundTag tag = RegionFile.fromPath(world.name(), location).decode(location);
184        if (tag == null) {
185            return false;
186        }
187
188        load(tag);
189        return true;
190    }
191
192    public void paint(boolean withLock) {
193        // If the state is not 0x00 it is either generating (-1) or has already been
194        if (!terrainPopulated.compareAndSet(0x00, 0xFFFFFFFF)) {
195            return;
196        }
197
198        List<FeatureGenerator> brushes = world.loader().brushes();
199        // init chunk event
200        ConcurrentHashMap<ChunkLocation, TridentChunk> localCache = new ConcurrentHashMap<>();
201        ChunkManipulator manipulator = new ChunkManipulator() {
202            @Override
203            public void manipulate(int relX, int y, int relZ, Substance substance, byte data) {
204                if (relX >= 0 && relX <= 15 && relZ >= 0 && relZ <= 15) {
205                    int index = WorldUtils.blockArrayIndex(relX & 15, y & 15, relZ & 15);
206                    ChunkSection section = sections.get(WorldUtils.section(y));
207                    section.types[index] = (char) (substance.asExtended() & 0xfff0 | data);
208                    NibbleArray.set(section.data, index, data);
209                    NibbleArray.set(section.skyLight, index, (byte) 255);
210                    NibbleArray.set(section.blockLight, index, (byte) 255);
211                    return;
212                }
213
214                int cx = location.x();
215                int cz = location.z();
216
217                int xMinDiff = Math.max(relX, 0) - Math.min(relX, 0);
218                int xMaxDiff = Math.max(relX, 15) - Math.min(relX, 15);
219                int zMinDiff = Math.max(relZ, 0) - Math.min(relZ, 0);
220                int zMaxDiff = Math.max(relZ, 15) - Math.min(relZ, 15);
221
222                int chunkX = location.x();
223                int chunkZ = location.z();
224                int newX = relX;
225                int newZ = relZ;
226
227                if (relX < 0) {
228                    newX = 16 - xMinDiff;
229                    chunkX = cx - up(xMinDiff / 16) - 1;
230                } else if (relX > 15) {
231                    newX = xMaxDiff - 1;
232                    chunkX = cx + up(xMaxDiff / 16) + 1;
233                }
234
235                if (relZ < 0){
236                    newZ = 16 - zMinDiff;
237                    chunkZ = cz - up(zMinDiff / 16) - 1;
238                } else if (relZ > 15) {
239                    newZ = zMaxDiff - 1;
240                    chunkZ = cz + up(zMaxDiff / 16) + 1;
241                }
242
243                ChunkLocation loc = ChunkLocation.create(chunkX, chunkZ);
244                TridentChunk chunk = localCache.computeIfAbsent(loc, k -> rawChunk(loc));
245                chunk.setAt(newX, y, newZ, substance, data, (byte) 255, (byte) 15);
246            }
247
248            @Override
249            public Block blockAt(int relX, int y, int relZ) {
250                if (relX >= 0 && relX <= 15 && relZ >= 0 && relZ <= 15) {
251                    ChunkSection section = sections.get(WorldUtils.section(y));
252                    int index = WorldUtils.blockArrayIndex(relX, y & 15, relZ);
253                    byte b = (byte) (section.types[index] >> 4);
254                    byte meta = (byte) (section.types[index] & 0xF);
255
256                    Substance material = Substance.fromId(b);
257
258                    if (material == null) {
259                        material = Substance.AIR; // check if valid
260                    }
261
262                    TridentBlock block = new TridentBlock(Position.create(world, relX + x() * 16, y, relZ + z() * 16),
263                            material, meta);
264                    Vector key = new Vector(relX, y, relZ);
265                    List<BlockMeta> metas = blockMeta.get(key);
266                    if (metas != null) {
267                        for (BlockMeta m : metas) {
268                            block.applyMeta(m);
269                        }
270                    }
271
272                    return block;
273                }
274
275                int cx = location.x();
276                int cz = location.z();
277
278                int xMinDiff = Math.max(relX, 0) - Math.min(relX, 0);
279                int xMaxDiff = Math.max(relX, 15) - Math.min(relX, 15);
280                int zMinDiff = Math.max(relZ, 0) - Math.min(relZ, 0);
281                int zMaxDiff = Math.max(relZ, 15) - Math.min(relZ, 15);
282
283                int chunkX = location.x();
284                int chunkZ = location.z();
285                int newX = relX;
286                int newZ = relZ;
287
288                if (relX < 0) {
289                    newX = 16 - xMinDiff;
290                    chunkX = cx - up(xMinDiff / 16) - 1;
291                } else if (relX > 15) {
292                    newX = xMaxDiff - 1;
293                    chunkX = cx + up(xMaxDiff / 16) + 1;
294                }
295
296                if (relZ < 0){
297                    newZ = 16 - zMinDiff;
298                    chunkZ = cz - up(zMinDiff / 16) - 1;
299                } else if (relZ > 15) {
300                    newZ = zMaxDiff - 1;
301                    chunkZ = cz + up(zMaxDiff / 16) + 1;
302                }
303
304                ChunkLocation loc = ChunkLocation.create(chunkX, chunkZ);
305                TridentChunk chunk = localCache.computeIfAbsent(loc, k -> rawChunk(loc));
306                return chunk.blockAt(newX, y, newZ);
307            }
308        };
309
310        if (withLock) sections.lockFully();
311        try {
312            for (int i = 0; i < 16; i++) {
313                for (int j = 0; j < 16; j++) {
314                    for (FeatureGenerator brush : brushes) {
315                        brush.generate(location, i, j, world.random(), heights, manipulator);
316                    }
317                }
318            }
319        } finally {
320            if (withLock) sections.release();
321        }
322
323        // Label as populated, so the chunk is not repopulated
324        terrainPopulated.set(0x01);
325    }
326
327    private TridentChunk rawChunk(ChunkLocation location) {
328        return world.chunkAt(location, true);
329    }
330
331    private static int up(double d) {
332        if (Math.rint(d) != d)
333            return (int) d + 1;
334        return (int) d;
335    }
336
337    public int maxHeightAt(int x, int z) {
338        return heights.get(WorldUtils.heightIndex(x, z));
339    }
340
341    @Override
342    public int x() {
343        return location.x();
344    }
345
346    @Override
347    public int z() {
348        return location.z();
349    }
350
351    @Override
352    public ChunkLocation location() {
353        return location;
354    }
355
356    @Override
357    public TridentWorld world() {
358        return world;
359    }
360
361    @Override
362    public Block blockAt(int relX, int y, int relZ) {
363        int index = WorldUtils.blockArrayIndex(relX, y & 15, relZ);
364        int sectionIndex = WorldUtils.section(y);
365        return sections.modifyAndReturn(sectionIndex, section -> {
366            /* Get block data; use extras accordingly */
367            byte b = (byte) (section.types[index] >> 4);
368            byte meta = (byte) (section.types[index] & 0xF);
369
370            Substance material = Substance.fromId(b);
371
372            if (material == null) {
373                material = Substance.AIR; // check if valid
374            }
375
376            TridentBlock block = new TridentBlock(Position.create(world, relX + x() * 16, y, relZ + z() * 16),
377                    material, meta);
378            Vector key = new Vector(relX, y, relZ);
379            List<BlockMeta> metas = blockMeta.get(key);
380            if (metas != null) {
381                for (BlockMeta m : metas) {
382                    block.applyMeta(m);
383                }
384            }
385
386            return block;
387        });
388    }
389
390    @Override
391    public ChunkSnapshot snapshot() {
392        return new TridentChunkSnapshot(world, this);
393    }
394
395    public PacketPlayOutChunkData asPacket() {
396        sections.lockFully();
397        try {
398            ByteArrayOutputStream data = new ByteArrayOutputStream();
399
400            for (int i = 0; i < 16; i++) {
401                ChunkSection section = sections.get(i);
402                if (section == null) {
403                    continue;
404                }
405
406                for (char c : section.types()) {
407                    data.write(c & 0xff);
408                    data.write(c >> 8);
409                }
410            }
411
412            for (int i = 0; i < 16; i++) {
413                ChunkSection section = sections.get(i);
414                try {
415                    if (section == null) {
416                        data.write(0);
417                        continue;
418                    }
419
420                    data.write(section.blockLight);
421                } catch (IOException e) {
422                    TridentLogger.get().error(e);
423                }
424            }
425
426            for (int i = 0; i < 16; i++) {
427                ChunkSection section = sections.get(i);
428                try {
429                    if (section == null) {
430                        data.write(0);
431                        continue;
432                    }
433
434                    data.write(section.skyLight);
435                } catch (IOException e) {
436                    TridentLogger.get().error(e);
437                }
438            }
439
440            for (int i = 0; i < 256; i += 1) {
441                data.write(0);
442            }
443
444            // fixme unused value
445            int bitmask = 65535;
446            return new PacketPlayOutChunkData(data.toByteArray(), location, false, (short) bitmask);
447        } finally {
448            sections.release();
449        }
450    }
451
452    public void load(CompoundTag root) {
453        CompoundTag tag = root.getTagAs("Level");
454        LongTag lastModifed = tag.getTagAs("LastUpdate");
455        ByteTag lightPopulated = tag.containsTag("LightPopulated") ? (ByteTag) tag.getTagAs(
456                "LightPopulated") : new ByteTag("LightPopulated").setValue((byte) 0);
457        ByteTag terrainPopulated = tag.getTagAs("TerrainPopulated");
458
459        LongTag inhabitedTime = tag.getTagAs("InhabitedTime");
460        IntArrayTag biomes = tag.getTagAs("HeightMap");
461
462        int[] rawHeight = biomes.value();
463        for (int i = 0; i < 256; i++) {
464            heights.set(i, rawHeight[i]);
465        }
466
467        ListTag sectionTags = tag.getTagAs("Sections");
468        ListTag entities = tag.getTagAs("Entities");
469        ListTag tileEntities = tag.containsTag("TileEntities") ? (ListTag) tag.getTag("TileEntities") :
470                new ListTag("TileEntities", TagType.COMPOUND);
471        ListTag tileTicks = tag.containsTag("TileTicks") ? (ListTag) tag.getTag("TileTicks") : new ListTag(
472                "TileTicks", TagType.COMPOUND);
473        List<NBTTag> sectionsList = sectionTags.listTags();
474
475        /* Load sections */
476        sections.lockFully();
477        try {
478            for (int i = 0; i < sectionsList.size(); i += 1) {
479                NBTTag t = sectionTags.getTag(i);
480
481                if (t instanceof CompoundTag) {
482                    CompoundTag ct = (CompoundTag) t;
483
484                    ChunkSection section = NBTSerializer.deserialize(ChunkSection.class, ct);
485                    section.loadBlocks();
486                    sections.set(section.y(), section);
487                }
488            }
489        } finally {
490            sections.release();
491        }
492
493        for (NBTTag t : entities.listTags()) {
494            //TridentEntity entity = EntityBuilder.create().build(TridentEntity.class);
495
496            //entity.load((CompoundTag) t);
497            //world.entities().add(entity);
498        }
499
500        /* Load extras */
501        this.lightPopulated.set(lightPopulated.value()); // Unknown use
502        this.terrainPopulated.set(terrainPopulated.value()); // if chunk was populated with special things (ores,
503        // trees, etc.), if 1 regenerate
504        lastModified = lastModifed.value(); // Tick when the chunk was last saved
505        this.inhabitedTime = inhabitedTime.value(); // Cumulative number of ticks player have been in the chunk
506    }
507
508    @Override
509    // todo refactor to boolean
510    public void unload() {
511        sections.lockFully();
512        try {
513            ChunkHandler chunkHandler = world.chunkHandler();
514
515            // slight inefficacy here - redundant operation when called with
516            // ChunkHandler#tryRemove(...)
517            CRefCounter refCounter = chunkHandler.get(location);
518            if (refCounter != null) {
519                if (refCounter.hasStrongRefs()) {
520                    return;
521                }
522            }
523
524            world.loader().saveChunk(this);
525            chunkHandler.remove(location);
526        } finally {
527            sections.release();
528        }
529    }
530
531    public CompoundTag asNbt() {
532        CompoundTag root = new CompoundTag("root");
533        CompoundTag level = new CompoundTag("Level");
534
535        level.addTag(new LongTag("LastUpdate").setValue(world.time()));
536        level.addTag(new ByteTag("LightPopulated").setValue((byte) lightPopulated.get()));
537        level.addTag(new ByteTag("TerrainPopulated").setValue((byte) terrainPopulated.get()));
538
539        level.addTag(new LongTag("InhabitedTime").setValue(inhabitedTime));
540
541        int[] rawHeights = new int[256];
542        for (int i = 0; i < 256; i++) {
543            rawHeights[i] = heights.get(i);
544        }
545
546        level.addTag(new IntArrayTag("HeightMap").setValue(rawHeights));
547
548        ListTag sectionTags = new ListTag("Sections", TagType.COMPOUND);
549
550        for (int i = 0; i < 16; i++) {
551            sections.modify(i, section -> {
552                section.updateRaw();
553                sectionTags.addTag(NBTSerializer.serialize(section));
554            });
555        }
556
557        level.addTag(sectionTags);
558
559        ListTag tag = new ListTag("Entities", TagType.COMPOUND);
560        for (Entity entity : entities()) {
561            tag.addTag(((TridentEntity) entity).asNbt());
562        }
563        level.addTag(tag);
564
565        root.addTag(level);
566
567        return root;
568    }
569
570    public void setAt(Position p, Substance type, byte metaData, byte skyLight, byte blockLight) {
571        setAt((int) p.x(), (int) p.y(), (int) p.z(), type, metaData, skyLight, blockLight);
572    }
573
574    public void setAt(int x, int y, int z, Substance type, byte metaData, byte skyLight,
575                      byte blockLight) {
576        int index = WorldUtils.blockArrayIndex(x & 15, y & 15, z & 15);
577        sections.modify(WorldUtils.section(y), section -> {
578            section.types[index] = (char) (type.asExtended() & 0xfff0 | metaData);
579            NibbleArray.set(section.data, index, metaData);
580            NibbleArray.set(section.skyLight, index, skyLight);
581            NibbleArray.set(section.blockLight, index, blockLight);
582        });
583    }
584
585    @Override
586    public ArrayList<Entity> getEntities(Entity exclude, BoundingBox boundingBox, Predicate<? super Entity> predicate){
587        return new ArrayList<>(entities.stream()
588                .filter(checking -> !checking.equals(exclude) && checking.boundingBox().collidesWith(boundingBox))
589                .filter(checking -> predicate == null || predicate.test(checking))
590                .collect(Collectors.toList()));
591    }
592}