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}