basin/basin/src/world.c

1531 lines
53 KiB
C

/*
* world.c
*
* Created on: Feb 22, 2016
* Author: root
*/
#include "hashmap.h"
#include "entity.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "collection.h"
#include <pthread.h>
#include "player.h"
#include "nbt.h"
#include <linux/limits.h>
#include <fcntl.h>
#include <stdio.h>
#include "util.h"
#include <unistd.h>
#include <sys/mman.h>
#include <zlib.h>
#include <dirent.h>
#include "worldmanager.h"
#include "network.h"
#include "packet.h"
#include "game.h"
#include "block.h"
#include <math.h>
#include "queue.h"
#include "tileentity.h"
#include "xstring.h"
#include "entity.h"
#include "world.h"
#include "globals.h"
#include "profile.h"
#include <errno.h>
#include "server.h"
int __boundingbox_intersects(struct boundingbox* bb1, struct boundingbox* bb2, int inv) {
return (((bb1->minX >= bb2->minX && bb1->minX <= bb2->maxX) || (bb1->maxX >= bb2->minX && bb1->maxX <= bb2->maxX)) && ((bb1->minY >= bb2->minY && bb1->minY <= bb2->maxY) || (bb1->maxY >= bb2->minY && bb1->maxY <= bb2->maxY)) && ((bb1->minZ >= bb2->minZ && bb1->minZ <= bb2->maxZ) || (bb1->maxZ >= bb2->minZ && bb1->maxZ <= bb2->maxZ))) || (inv ? 0 : __boundingbox_intersects(bb2, bb1, 1)); // the second intersects handles if bb2 is contained within bb1
}
int boundingbox_intersects(struct boundingbox* bb1, struct boundingbox* bb2) {
return __boundingbox_intersects(bb1, bb2, 0);
}
uint64_t getChunkKey(struct chunk* ch) {
return (uint64_t)(((int64_t) ch->x) << 32) | (((int64_t) ch->z) & 0xFFFFFFFF);
}
uint64_t getChunkKey2(int32_t cx, int32_t cz) {
return (uint64_t)(((int64_t) cx) << 32) | (((int64_t) cz) & 0xFFFFFFFF);
}
struct chunk* getChunk_guess(struct world* world, struct chunk* ch, int32_t x, int32_t z) {
if (ch == NULL) return getChunk(world, x, z);
int32_t rx = x >> 4;
int32_t rz = z >> 4;
if (ch->x == rx && ch->z == rz) return ch;
if (abs(x - ch->x) > 3 || abs(z - ch->z) > 3) return getChunk(world, x, z);
struct chunk* cch = ch;
while (cch != NULL) {
if (cch->x > rx) cch = cch->xn;
else if (cch->x < rx) cch = cch->xp;
if (cch != NULL) {
if (cch->z > rz) cch = cch->zn;
else if (cch->z < rz) cch = cch->zp;
}
if (cch != NULL && cch->x == rx && rz) return cch;
}
return getChunk(world, x, z);
}
struct chunk* loadRegionChunk(struct region* region, int8_t lchx, int8_t lchz, size_t chri) {
if (region->fd[chri] < 0) {
region->fd[chri] = open(region->file, O_RDWR);
if (region->fd[chri] < 0) {
printf("Error opening region: %s\n", strerror(errno));
return NULL;
}
region->mapping[chri] = mmap(NULL, 67108864, PROT_READ | PROT_WRITE, MAP_SHARED, region->fd[chri], 0); // 64 MB is the theoretical limit of an uncompressed region file
if (region->mapping[chri] == NULL || region->mapping[chri] == (void*) -1) {
printf("Error mapping region: %s\n", strerror(errno));
}
}
uint32_t* hf = region->mapping[chri] + 4 * ((lchx & 31) + (lchz & 31) * 32);
uint32_t rhf = *hf;
swapEndian(&rhf, 4);
uint32_t off = ((rhf & 0xFFFFFF00) >> 8) * 4096;
uint32_t size = ((rhf & 0x000000FF)) * 4096;
if (off == 0 || size == 0) return NULL;
void* chk = region->mapping[chri] + (off);
uint32_t rsize = ((uint32_t*) chk)[0];
swapEndian(&rsize, 4);
uint8_t comptype = ((uint8_t*) chk)[4];
chk += 5;
void* rtbuf = xmalloc(65536);
size_t rtc = 65536;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
int dr = 0;
if ((dr = inflateInit2(&strm, (32 + MAX_WBITS))) != Z_OK) { //
printf("Compression initialization error!\n");
xfree(rtbuf);
return NULL;
}
strm.avail_in = rsize;
strm.next_in = chk;
strm.avail_out = rtc;
strm.next_out = rtbuf;
do {
if (rtc - strm.total_out < 32768) {
rtc += 65536;
rtbuf = xrealloc(rtbuf, rtc);
}
strm.avail_out = rtc - strm.total_out;
strm.next_out = rtbuf + strm.total_out;
dr = inflate(&strm, Z_FINISH);
if (dr == Z_STREAM_ERROR) {
printf("Compression Read Error!\n");
inflateEnd(&strm);
xfree(rtbuf);
return NULL;
}
} while (strm.avail_out == 0);
inflateEnd(&strm);
size_t rts = strm.total_out;
struct nbt_tag* nbt = NULL;
if (readNBT(&nbt, rtbuf, rts) < 0) {
xfree(rtbuf);
return NULL;
}
xfree(rtbuf);
struct nbt_tag* level = getNBTChild(nbt, "Level");
if (level == NULL || level->id != NBT_TAG_COMPOUND) goto rerx;
struct nbt_tag* tmp = getNBTChild(level, "xPos");
if (tmp == NULL || tmp->id != NBT_TAG_INT) goto rerx;
int32_t xPos = tmp->data.nbt_int;
tmp = getNBTChild(level, "zPos");
if (tmp == NULL || tmp->id != NBT_TAG_INT) goto rerx;
int32_t zPos = tmp->data.nbt_int;
struct chunk* rch = newChunk(xPos, zPos);
tmp = getNBTChild(level, "LightPopulated");
if (tmp == NULL || tmp->id != NBT_TAG_BYTE) goto rerx;
rch->lightpopulated = tmp->data.nbt_byte;
tmp = getNBTChild(level, "TerrainPopulated");
if (tmp == NULL || tmp->id != NBT_TAG_BYTE) goto rerx;
rch->terrainpopulated = tmp->data.nbt_byte;
tmp = getNBTChild(level, "InhabitedTime");
if (tmp == NULL || tmp->id != NBT_TAG_LONG) goto rerx;
rch->inhabitedticks = tmp->data.nbt_long;
tmp = getNBTChild(level, "Biomes");
if (tmp == NULL || tmp->id != NBT_TAG_BYTEARRAY) goto rerx;
if (tmp->data.nbt_bytearray.len == 256) memcpy(rch->biomes, tmp->data.nbt_bytearray.data, 256);
tmp = getNBTChild(level, "HeightMap");
if (tmp == NULL || tmp->id != NBT_TAG_INTARRAY) goto rerx;
if (tmp->data.nbt_intarray.count == 256) for (int i = 0; i < 256; i++)
rch->heightMap[i >> 4][i & 0x0F] = (uint16_t) tmp->data.nbt_intarray.ints[i];
struct nbt_tag* tes = getNBTChild(level, "TileEntities");
if (tes == NULL || tes->id != NBT_TAG_LIST) goto rerx;
for (size_t i = 0; i < tes->children_count; i++) {
struct nbt_tag* ten = tes->children[i];
if (ten == NULL || ten->id != NBT_TAG_COMPOUND) continue;
struct tile_entity* te = parseTileEntity(ten);
add_collection(rch->tileEntities, te);
}
//TODO: tileticks
struct nbt_tag* sections = getNBTChild(level, "Sections");
for (size_t i = 0; i < sections->children_count; i++) {
struct nbt_tag* section = sections->children[i];
tmp = getNBTChild(section, "Y");
if (tmp == NULL || tmp->id != NBT_TAG_BYTE) continue;
uint8_t y = tmp->data.nbt_byte;
tmp = getNBTChild(section, "Blocks");
if (tmp == NULL || tmp->id != NBT_TAG_BYTEARRAY || tmp->data.nbt_bytearray.len != 4096) continue;
int hna = 0;
block* rbl = xmalloc(sizeof(block) * 4096);
for (int i = 0; i < 4096; i++) {
rbl[i] = ((block) tmp->data.nbt_bytearray.data[i]) << 4; // [i >> 8][(i & 0xf0) >> 4][i & 0x0f]
if (((block) tmp->data.nbt_bytearray.data[i]) != 0) hna = 1;
}
//if (hna) rch->empty[y] = 0;
tmp = getNBTChild(section, "Add");
if (tmp != NULL) {
if (tmp->id != NBT_TAG_BYTEARRAY || tmp->data.nbt_bytearray.len != 2048) continue;
for (int i = 0; i < 4096; i++) {
block sx = tmp->data.nbt_bytearray.data[i / 2];
if (i % 2 == 0) {
sx &= 0xf0;
sx >>= 4;
} else sx &= 0x0f;
sx <<= 8;
rbl[i] |= sx;
if (rbl[i] != 0) hna = 1;
}
}
if (hna) {
tmp = getNBTChild(section, "Data");
if (tmp == NULL || tmp->id != NBT_TAG_BYTEARRAY || tmp->data.nbt_bytearray.len != 2048) continue;
for (int i = 0; i < 4096; i++) {
block sx = tmp->data.nbt_bytearray.data[i / 2];
if (i % 2 == 1) {
sx &= 0xf0;
sx >>= 4;
} else sx &= 0x0f;
rbl[i] |= sx;
}
struct chunk_section* cs = xmalloc(sizeof(struct chunk_section));
cs->palette = xmalloc(256 * sizeof(block));
cs->palette_count = 0;
block ipalette[getBlockSize()];
block l = 0;
for (int i = 0; i < 4096; i++) {
if (rbl[i] != l) {
for (int x = cs->palette_count - 1; x >= 0; x--) {
if (cs->palette[x] == rbl[i]) goto cx;
}
ipalette[rbl[i]] = cs->palette_count;
cs->palette[cs->palette_count++] = rbl[i]; // TODO: if only a few blocks of a certain type and on a palette boundary, use a MBC to reduce size?
if (cs->palette_count >= 256) {
xfree(cs->palette);
cs->palette = NULL;
cs->bpb = 13;
cs->palette_count = 0;
break;
}
l = rbl[i];
}
cx: ;
}
if (cs->palette != NULL) {
cs->bpb = (uint8_t) ceil(log2(cs->palette_count));
if (cs->bpb < 4) cs->bpb = 4;
cs->palette = xrealloc(cs->palette, cs->palette_count * sizeof(block));
}
int32_t bi = 0;
cs->blocks = xmalloc(512 * cs->bpb + 4);
cs->block_size = 512 * cs->bpb;
cs->mvs = 0;
for (int i = 0; i < cs->bpb; i++)
cs->mvs |= 1 << i;
for (int i = 0; i < 4096; i++) { // [i >> 8][(i & 0xf0) >> 4][i & 0x0f]
int32_t b = (int32_t)((cs->bpb == 13 ? rbl[i] : ipalette[rbl[i]]) & cs->mvs);
int32_t cv = *((int32_t*) (&cs->blocks[bi / 8]));
int32_t sbi = bi % 8;
cv = (cv & ~(cs->mvs << sbi)) | (b << sbi);
*((int32_t*) &cs->blocks[bi / 8]) = cv;
bi += cs->bpb;
}
tmp = getNBTChild(section, "BlockLight");
if (tmp != NULL) {
if (tmp->id != NBT_TAG_BYTEARRAY || tmp->data.nbt_bytearray.len != 2048) continue;
memcpy(cs->blockLight, tmp->data.nbt_bytearray.data, 2048);
}
tmp = getNBTChild(section, "SkyLight");
if (tmp != NULL) {
if (tmp->id != NBT_TAG_BYTEARRAY || tmp->data.nbt_bytearray.len != 2048) continue;
cs->skyLight = xmalloc(2048);
memcpy(cs->skyLight, tmp->data.nbt_bytearray.data, 2048);
}
rch->sections[y] = cs;
}
xfree(rbl);
}
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
if (rch->heightMap[z][x] <= 0) {
for (int y = 255; y >= 0; y--) {
block b = getBlockChunk(rch, x, y, z);
struct block_info* bi = getBlockInfo(b);
if (bi == NULL || bi->lightOpacity <= 0) continue;
rch->heightMap[z][x] = y + 1;
break;
}
}
}
}
//TODO: entities and tileticks.
freeNBT(nbt);
return rch;
rerx: ;
freeNBT(nbt);
return NULL;
}
void generateChunk(struct world* world, struct chunk* chunk) {
memset(chunk->sections, 0, sizeof(struct chunk_section*) * 16);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 64; y++)
setBlockChunk(chunk, y < 5 ? BLK_BEDROCK : (y == 63 ? BLK_GRASS : (y >= 60 ? BLK_DIRT : BLK_STONE)), x, y, z, world->dimension == OVERWORLD);
}
}
}
struct chunk* getChunkWithLoad(struct world* world, int32_t x, int32_t z, size_t chri) {
struct chunk* ch = get_hashmap(world->chunks, getChunkKey2(x, z));
if (ch != NULL) return ch;
int16_t rx = x >> 5;
int16_t rz = z >> 5;
uint64_t ri = (((uint64_t)(rx) & 0xFFFF) << 16) | (((uint64_t) rz) & 0xFFFF);
struct region* ar = get_hashmap(world->regions, ri);
if (ar == NULL) {
char lp[PATH_MAX];
snprintf(lp, PATH_MAX, "%s/region/r.%i.%i.mca", world->lpa, rx, rz);
ar = newRegion(lp, rx, rz, world->chl_count);
put_hashmap(world->regions, ri, ar);
//TODO: populate region
}
struct chunk* chk = NULL;
chk = loadRegionChunk(ar, x & 0x1F, z & 0x1F, chri);
if (chk != NULL) {
chk->xp = getChunk(world, x + 1, z);
if (chk->xp != NULL) chk->xp->xn = chk;
chk->xn = getChunk(world, x - 1, z);
if (chk->xn != NULL) chk->xn->xp = chk;
chk->zp = getChunk(world, x, z + 1);
if (chk->zp != NULL) chk->zp->zn = chk;
chk->zn = getChunk(world, x, z - 1);
if (chk->zn != NULL) chk->zn->zp = chk;
put_hashmap(world->chunks, getChunkKey(chk), chk);
} else goto gen;
return chk;
gen: ;
struct chunk* gc = newChunk(x, z);
generateChunk(world, gc);
if (gc != NULL) put_hashmap(world->chunks, getChunkKey(gc), gc);
if (!ar) {
char lp[PATH_MAX];
snprintf(lp, PATH_MAX, "%s/region/r.%i.%i.mca", world->lpa, rx, rz);
ar = newRegion(lp, rx, rz, world->chl_count);
put_hashmap(world->regions, ri, ar);
}
return gc;
}
void chunkloadthr(size_t b) {
size_t rem = 1;
while (1) {
if (--rem <= 0) {
nm: ;
pthread_mutex_lock (&chunk_wake_mut);
pthread_cond_wait(&chunk_wake, &chunk_wake_mut);
pthread_mutex_unlock(&chunk_wake_mut);
rem = chunk_input->size;
if (rem == 0) goto nm;
}
pthread_mutex_lock(&chunk_input->data_mutex);
struct chunk_req* chr = pop_nowait_queue(chunk_input);
if (chr == NULL) {
pthread_mutex_unlock(&chunk_input->data_mutex);
continue;
}
if (chr->pl->defunct) {
xfree(chr);
pthread_mutex_unlock(&chunk_input->data_mutex);
continue;
}
if (chr->pl->chunksSent >= 3 || (chr->pl->conn != NULL && chr->pl->conn->writeBuffer_size > 1024 * 1024 * 128)) {
add_queue(chunk_input, chr);
pthread_mutex_unlock(&chunk_input->data_mutex);
continue;
}
pthread_mutex_unlock(&chunk_input->data_mutex);
if (chr->load) chr->pl->chunksSent++;
if (chr->load) {
if (contains_hashmap(chr->pl->loadedChunks, getChunkKey2(chr->cx, chr->cz))) {
xfree(chr);
continue;
}
beginProfilerSection("chunkLoading_getChunk");
struct chunk* ch = getChunkWithLoad(chr->pl->world, chr->cx, chr->cz, b);
if (chr->pl->loadedChunks == NULL) {
xfree(chr);
continue;
}
endProfilerSection("chunkLoading_getChunk");
if (ch != NULL) {
ch->playersLoaded++;
//beginProfilerSection("chunkLoading_sendChunk_add");
put_hashmap(chr->pl->loadedChunks, getChunkKey(ch), ch);
//endProfilerSection("chunkLoading_sendChunk_add");
//beginProfilerSection("chunkLoading_sendChunk_malloc");
struct packet* pkt = xmalloc(sizeof(struct packet));
pkt->id = PKT_PLAY_CLIENT_CHUNKDATA;
pkt->data.play_client.chunkdata.data = ch;
pkt->data.play_client.chunkdata.cx = ch->x;
pkt->data.play_client.chunkdata.cz = ch->z;
pkt->data.play_client.chunkdata.ground_up_continuous = 1;
pkt->data.play_client.chunkdata.number_of_block_entities = ch->tileEntities->count;
//endProfilerSection("chunkLoading_sendChunk_malloc");
//beginProfilerSection("chunkLoading_sendChunk_tileEntities");
pkt->data.play_client.chunkdata.block_entities = ch->tileEntities->count == 0 ? NULL : xmalloc(sizeof(struct nbt_tag*) * ch->tileEntities->count);
size_t ri = 0;
for (size_t i = 0; i < ch->tileEntities->size; i++) {
if (ch->tileEntities->data[i] == NULL) continue;
struct tile_entity* te = ch->tileEntities->data[i];
pkt->data.play_client.chunkdata.block_entities[ri++] = serializeTileEntity(te, 1);
}
//endProfilerSection("chunkLoading_sendChunk_tileEntities");
//beginProfilerSection("chunkLoading_sendChunk_dispatch");
add_queue(chr->pl->outgoingPacket, pkt);
flush_outgoing(chr->pl);
//endProfilerSection("chunkLoading_sendChunk_dispatch");
}
} else {
//beginProfilerSection("unchunkLoading");
struct packet* pkt = xmalloc(sizeof(struct packet));
pkt->id = PKT_PLAY_CLIENT_UNLOADCHUNK;
pkt->data.play_client.unloadchunk.chunk_x = chr->cx;
pkt->data.play_client.unloadchunk.chunk_z = chr->cz;
pkt->data.play_client.unloadchunk.ch = NULL;
struct chunk* ch = getChunk(chr->pl->world, chr->cx, chr->cz);
put_hashmap(chr->pl->loadedChunks, getChunkKey2(chr->cx, chr->cz), NULL);
if (ch != NULL && !ch->defunct) {
if (--ch->playersLoaded <= 0) {
unloadChunk(chr->pl->world, ch);
}
}
add_queue(chr->pl->outgoingPacket, pkt);
flush_outgoing(chr->pl);
//endProfilerSection("unchunkLoading");
}
xfree(chr);
}
}
struct chunk* getChunk(struct world* world, int32_t x, int32_t z) {
struct chunk* ch = get_hashmap(world->chunks, getChunkKey2(x, z));
return ch;
}
int isChunkLoaded(struct world* world, int32_t x, int32_t z) {
return contains_hashmap(world->chunks, getChunkKey2(x, z));
}
void unloadChunk(struct world* world, struct chunk* chunk) {
//TODO: save chunk
//pthread_rwlock_wrlock(&world->chl);
if (chunk->xp != NULL) chunk->xp->xn = NULL;
if (chunk->xn != NULL) chunk->xn->xp = NULL;
if (chunk->zp != NULL) chunk->zp->zn = NULL;
if (chunk->zn != NULL) chunk->zn->zp = NULL;
put_hashmap(world->chunks, getChunkKey(chunk), NULL);
add_collection(defunctChunks, chunk);
//pthread_rwlock_unlock(&world->chl);
}
int getBiome(struct world* world, int32_t x, int32_t z) {
struct chunk* chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return 0;
return chunk->biomes[z & 0x0f][x & 0x0f];
}
block getBlockChunk(struct chunk* chunk, uint8_t x, uint8_t y, uint8_t z) {
if (x > 15 || z > 15 || y > 255 || x < 0 || z < 0 || y < 0) return 0;
struct chunk_section* cs = chunk->sections[y >> 4];
if (cs == NULL) return 0;
uint32_t i = ((y & 0x0f) << 8) | (z << 4) | x;
uint32_t bi = cs->bpb * i;
int32_t rcv = *((int32_t*) (&cs->blocks[bi / 8]));
int32_t rsbi = bi % 8;
block b = (rcv >> rsbi) & cs->mvs;
if (cs->palette != NULL && b < cs->palette_count) b = cs->palette[b];
return b;
}
uint8_t getLightChunk(struct chunk* chunk, uint8_t x, uint8_t y, uint8_t z, uint8_t subt) {
if (x > 15 || z > 15 || y > 255 || x < 0 || z < 0 || y < 0) return 0;
struct chunk_section* cs = chunk->sections[y >> 4];
if (cs == NULL) return 0;
uint32_t i = ((y & 0x0f) << 8) | (z << 4) | x;
uint32_t bi = 4 * i;
int8_t skl = 0;
if (cs->skyLight != NULL) {
uint8_t tskl = cs->skyLight[bi / 8];
if (i % 2 == 1) {
tskl &= 0xf0;
tskl >>= 4;
} else tskl &= 0x0f;
skl = tskl;
}
skl -= subt;
uint8_t bl = cs->blockLight[bi / 8];
if (i % 2 == 1) {
bl &= 0xf0;
bl >>= 4;
} else bl &= 0x0f;
if (bl > skl) skl = bl;
if (skl < 0) skl = 0;
return skl;
}
uint8_t getRawLightChunk(struct chunk* chunk, uint8_t x, uint8_t y, uint8_t z, uint8_t blocklight) {
if (x > 15 || z > 15 || y > 255 || x < 0 || z < 0 || y < 0) return 0;
struct chunk_section* cs = chunk->sections[y >> 4];
if (cs == NULL) return 0;
uint32_t i = ((y & 0x0f) << 8) | (z << 4) | x;
uint32_t bi = 4 * i;
uint8_t* target = blocklight ? cs->blockLight : cs->skyLight;
uint8_t bl = target[bi / 8];
if (i % 2 == 1) {
bl &= 0xf0;
bl >>= 4;
} else bl &= 0x0f;
return bl;
}
void setLightChunk(struct chunk* chunk, uint8_t light, uint8_t x, uint8_t y, uint8_t z, uint8_t blocklight, uint8_t skylight) { // skylight is only for making new chunk sections, not related to set!
if (x > 15 || z > 15 || y > 255 || x < 0 || z < 0 || y < 0) return;
struct chunk_section* cs = chunk->sections[y >> 4];
if (cs == NULL) {
cs = newChunkSection(chunk, y >> 4, skylight);
}
uint32_t i = ((y & 0x0f) << 8) | (z << 4) | x;
uint32_t bi = 4 * i;
uint8_t* target = blocklight ? cs->blockLight : cs->skyLight;
uint8_t bl = target[bi / 8];
if (i % 2 == 1) {
bl &= 0x0f;
bl |= (light & 0x0f) << 4;
} else {
bl &= 0xf0;
bl |= light & 0x0f;
}
target[bi / 8] = bl;
}
uint8_t getLightWorld_guess(struct world* world, struct chunk* ch, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return 0;
ch = getChunk_guess(world, ch, x, z);
if (ch != NULL) return getLightChunk(ch, x & 0x0f, y, z & 0x0f, world->skylightSubtracted);
else return getLightWorld(world, x, y, z, 0);
}
uint8_t getRawLightWorld_guess(struct world* world, struct chunk* ch, int32_t x, int32_t y, int32_t z, uint8_t blocklight) {
if (y < 0 || y > 255) return 0;
ch = getChunk_guess(world, ch, x, z);
if (ch != NULL) return getRawLightChunk(ch, x & 0x0f, y, z & 0x0f, blocklight);
else return getRawLightWorld(world, x, y, z, blocklight);
}
uint16_t getHeightMapWorld_guess(struct world* world, struct chunk* ch, int32_t x, int32_t z) {
if (world->dimension != OVERWORLD) return 0;
ch = getChunk_guess(world, ch, x >> 4, z >> 4);
if (ch == NULL) return 0;
return ch->heightMap[z & 0x0f][x & 0x0f];
}
void setLightWorld_guess(struct world* world, struct chunk* ch, uint8_t light, int32_t x, int32_t y, int32_t z, uint8_t blocklight) {
if (y < 0 || y > 255) return;
ch = getChunk_guess(world, ch, x >> 4, z >> 4);
if (ch != NULL) return setLightChunk(ch, light & 0x0f, x & 0x0f, y, z & 0x0f, blocklight, world->dimension == 0);
else return setLightWorld(world, light & 0x0f, x, y, z, blocklight);
}
void setLightWorld(struct world* world, uint8_t light, int32_t x, int32_t y, int32_t z, uint8_t blocklight) {
if (y < 0 || y > 255) return;
struct chunk* chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return;
setLightChunk(chunk, light & 0x0f, x & 0x0f, y > 255 ? 255 : y, z & 0x0f, blocklight, world->dimension == 0);
}
uint8_t getLightWorld(struct world* world, int32_t x, int32_t y, int32_t z, uint8_t checkNeighbors) {
if (y < 0 || y > 255) return 0;
struct chunk* chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return 15;
if (checkNeighbors) {
uint8_t yp = getLightChunk(chunk, x & 0x0f, y, z & 0x0f, world->skylightSubtracted);
uint8_t xp = getLightWorld_guess(world, chunk, x + 1, y, z);
uint8_t xn = getLightWorld_guess(world, chunk, x - 1, y, z);
uint8_t zp = getLightWorld_guess(world, chunk, x, y, z + 1);
uint8_t zn = getLightWorld_guess(world, chunk, x, y, z - 1);
if (xp > yp) yp = xp;
if (xn > yp) yp = xn;
if (zp > yp) yp = zp;
if (zn > yp) yp = zn;
return yp;
} else if (y < 0) return 0;
else {
return getLightChunk(chunk, x, y > 255 ? 255 : y, z, world->skylightSubtracted);
}
}
uint8_t getRawLightWorld(struct world* world, int32_t x, int32_t y, int32_t z, uint8_t blocklight) {
if (y < 0 || y > 255) return 0;
struct chunk* chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return 15;
return getRawLightChunk(chunk, x & 0x0f, y > 255 ? 255 : y, z & 0x0f, blocklight);
}
block getBlockWorld(struct world* world, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return 0;
struct chunk* chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return 0;
return getBlockChunk(chunk, x & 0x0f, y, z & 0x0f);
}
block getBlockWorld_guess(struct world* world, struct chunk* ch, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return 0;
ch = getChunk_guess(world, ch, x >> 4, z >> 4);
if (ch != NULL) return getBlockChunk(ch, x & 0x0f, y, z & 0x0f);
else return getBlockWorld(world, x, y, z);
}
struct tile_entity* getTileEntityChunk(struct chunk* chunk, int32_t x, int32_t y, int32_t z) { // TODO: optimize
if (y > 255 || y < 0) return NULL;
for (size_t i = 0; i < chunk->tileEntities->size; i++) {
struct tile_entity* te = (struct tile_entity*) chunk->tileEntities->data[i];
if (te == NULL) continue;
if (te->x == x && te->y == y && te->z == z) return te;
}
return NULL;
}
void setTileEntityChunk(struct chunk* chunk, struct tile_entity* te) { // TODO: optimize
if (te == NULL) return;
int32_t x = te->x;
uint8_t y = te->y;
int32_t z = te->z;
if (y > 255 || y < 0) return;
for (size_t i = 0; i < chunk->tileEntities->size; i++) {
struct tile_entity* te2 = (struct tile_entity*) chunk->tileEntities->data[i];
if (te2 == NULL) continue;
if (te2->x == x && te2->y == y && te2->z == z) {
if (te2->tick) rem_collection(chunk->tileEntitiesTickable, te2);
freeTileEntity(te2);
chunk->tileEntities->data[i] = te;
if (te == NULL) {
chunk->tileEntities->count--;
if (i == chunk->tileEntities->size - 1) chunk->tileEntities->size--;
} else if (te->tick) add_collection(chunk->tileEntitiesTickable, te);
return;
}
}
add_collection(chunk->tileEntities, te);
if (te->tick) add_collection(chunk->tileEntitiesTickable, te);
}
void enableTileEntityTickable(struct world* world, struct tile_entity* te) {
if (te == NULL || te->y > 255 || te->y < 0) return;
struct chunk* chunk = getChunk(world, te->x >> 4, te->z >> 4);
if (chunk == NULL) return;
add_collection(chunk->tileEntitiesTickable, te);
}
void disableTileEntityTickable(struct world* world, struct tile_entity* te) {
if (te == NULL || te->y > 255 || te->y < 0) return;
struct chunk* chunk = getChunk(world, te->x >> 4, te->z >> 4);
if (chunk == NULL) return;
rem_collection(chunk->tileEntitiesTickable, te);
}
struct tile_entity* getTileEntityWorld(struct world* world, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return NULL;
struct chunk* chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return NULL;
return getTileEntityChunk(chunk, x, y, z);
}
void setTileEntityWorld(struct world* world, int32_t x, int32_t y, int32_t z, struct tile_entity* te) {
if (y < 0 || y > 255) return;
struct chunk* chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return;
setTileEntityChunk(chunk, te);
}
struct chunk* newChunk(int16_t x, int16_t z) {
struct chunk* chunk = xmalloc(sizeof(struct chunk));
memset(chunk, 0, sizeof(struct chunk));
chunk->x = x;
chunk->z = z;
memset(chunk->sections, 0, sizeof(struct chunk_section*) * 16);
chunk->playersLoaded = 0;
chunk->tileEntities = new_collection(0, 0);
chunk->tileEntitiesTickable = new_collection(0, 0);
chunk->defunct = 0;
chunk->xp = NULL;
chunk->xn = NULL;
chunk->zp = NULL;
chunk->zn = NULL;
//chunk->entities = new_hashmap(1, 0);
return chunk;
}
void freeChunk(struct chunk* chunk) {
for (int i = 0; i < 16; i++) {
struct chunk_section* cs = chunk->sections[i];
if (cs == NULL) continue;
if (cs->blocks != NULL) xfree(cs->blocks);
if (cs->palette != NULL) xfree(cs->palette);
if (cs->skyLight != NULL) xfree(cs->skyLight);
xfree(cs);
}
for (size_t i = 0; i < chunk->tileEntities->size; i++) {
if (chunk->tileEntities->data[i] != NULL) {
freeTileEntity(chunk->tileEntities->data[i]);
}
}
del_collection(chunk->tileEntities);
del_collection(chunk->tileEntitiesTickable);
//BEGIN_HASHMAP_ITERATION(chunk->entities)
//freeEntity (value);
//END_HASHMAP_ITERATION(chunk->entities)
//del_hashmap(chunk->entities);
xfree(chunk);
}
void scheduleBlockTick(struct world* world, int32_t x, int32_t y, int32_t z, int32_t ticksFromNow) {
if (y < 0 || y > 255) return;
struct scheduled_tick* st = xmalloc(sizeof(struct scheduled_tick));
st->x = x;
st->y = y;
st->z = z;
st->ticksLeft = ticksFromNow;
put_hashmap(world->scheduledTicks, (uint64_t) st, st);
}
struct region* newRegion(char* path, int16_t x, int16_t z, size_t chr_count) {
struct region* reg = xcalloc(sizeof(struct region));
reg->x = x;
reg->z = z;
reg->file = xstrdup(path, 0);
reg->fd = xmalloc(chr_count * sizeof(int));
for (int i = 0; i < chr_count; i++)
reg->fd[i] = -1;
reg->mapping = xcalloc(chr_count * sizeof(void*));
return reg;
}
void freeRegion(struct region* region) {
xfree(region->fd);
xfree(region->mapping);
xfree(region->file);
xfree(region);
}
struct chunk_section* newChunkSection(struct chunk* chunk, int ymj, int skylight) {
chunk->sections[ymj] = xcalloc(sizeof(struct chunk_section));
struct chunk_section* cs = chunk->sections[ymj];
cs->bpb = 4;
cs->block_size = 512 * 4;
cs->blocks = xcalloc(512 * 4 + 4);
cs->palette_count = 1;
cs->palette = xmalloc(sizeof(block));
cs->palette[0] = BLK_AIR;
cs->mvs = 0xf;
memset(cs->blockLight, 0, 2048);
if (skylight) {
cs->skyLight = xmalloc(2048);
memset(cs->skyLight, 0xFF, 2048);
}
return cs;
}
void setBlockChunk(struct chunk* chunk, block blk, uint8_t x, uint8_t y, uint8_t z, int skylight) {
if (x > 15 || z > 15 || y > 255 || x < 0 || z < 0 || y < 0) return;
struct chunk_section* cs = chunk->sections[y >> 4];
if (cs == NULL && blk != 0) {
cs = newChunkSection(chunk, y >> 4, skylight);
} else if (cs == NULL) return;
if (skylight) {
struct block_info* bii = getBlockInfo(blk);
if (bii != NULL) {
if (bii->lightOpacity >= 1) {
if (chunk->heightMap[z][x] <= y) chunk->heightMap[z][x] = y + 1;
else {
for (int ny = chunk->heightMap[z][x] - 1; ny >= 0; ny--) {
struct block_info* nbi = getBlockInfo(getBlockChunk(chunk, x, y, z));
if (nbi != NULL && nbi->lightOpacity >= 1) {
chunk->heightMap[z][x] = ny + 1;
break;
}
}
}
} else if (chunk->heightMap[z][x] > y) {
for (int ny = chunk->heightMap[z][x] - 1; ny >= 0; ny--) {
struct block_info* nbi = getBlockInfo(getBlockChunk(chunk, x, y, z));
if (nbi != NULL && nbi->lightOpacity >= 1) {
chunk->heightMap[z][x] = ny + 1;
break;
}
}
}
}
}
block ts = blk;
if (cs->bpb < 9) {
for (int i = 0; i < cs->palette_count; i++) {
if (cs->palette[i] == blk) {
ts = i;
goto pp;
}
}
uint32_t room = pow(2, cs->bpb) - cs->palette_count;
if (room < 1) {
uint8_t nbpb = cs->bpb + 1;
if (nbpb >= 9) nbpb = 13;
uint8_t* ndata = xcalloc(nbpb * 512 + 4);
uint32_t bir = 0;
uint32_t biw = 0;
int32_t nmvs = cs->mvs | (1 << (nbpb - 1));
if (nbpb == 13) nmvs = 0x1FFF;
for (int i = 0; i < 4096; i++) {
int32_t rcv = *((int32_t*) (&cs->blocks[bir / 8]));
int32_t rsbi = bir % 8;
int32_t b = (rcv >> rsbi) & cs->mvs;
if (nbpb == 13) b = cs->palette[b];
int32_t wcv = *((int32_t*) (&ndata[biw / 8]));
int32_t wsbi = biw % 8;
wcv = (wcv & ~(nmvs << wsbi)) | (b << wsbi);
*((int32_t*) &ndata[biw / 8]) = wcv;
bir += cs->bpb;
biw += nbpb;
}
uint8_t* odata = cs->blocks;
cs->blocks = ndata;
cs->block_size = nbpb * 512;
xfree(odata);
cs->mvs = nmvs;
cs->bpb = nbpb;
}
ts = cs->palette_count;
cs->palette = xrealloc(cs->palette, sizeof(block) * (cs->palette_count + 1));
cs->palette[cs->palette_count++] = blk;
pp: ;
}
uint32_t i = ((y & 0x0f) << 8) | (z << 4) | x;
uint32_t bi = cs->bpb * i;
int32_t b = ((int32_t) ts) & cs->mvs;
int32_t cv = *((int32_t*) (&cs->blocks[bi / 8]));
int32_t sbi = bi % 8;
cv = (cv & ~(cs->mvs << sbi)) | (b << sbi);
*((int32_t*) &cs->blocks[bi / 8]) = cv;
}
void setBlockWorld(struct world* world, block blk, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return;
struct chunk* ch = getChunk(world, x >> 4, z >> 4);
if (ch == NULL) return;
setBlockWorld_guess(world, ch, blk, x, y, z);
}
void setBlockWorld_noupdate(struct world* world, block blk, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return;
struct chunk* ch = getChunk(world, x >> 4, z >> 4);
if (ch == NULL) return;
setBlockWorld_guess_noupdate(world, ch, blk, x, y, z);
}
void world_doLightProc(struct world* world, struct chunk* chunk, int32_t x, int32_t y, int32_t z, uint8_t light) {
uint8_t cl = getRawLightWorld_guess(world, chunk, x, y, z, 1);
if (cl < light) setLightWorld_guess(world, chunk, light & 0x0f, x, y, z, 1);
}
struct world_lightpos {
int32_t x;
int32_t y;
int32_t z;
};
int light_floodfill(struct world* world, struct chunk* chunk, struct world_lightpos* lp, int skylight, int subtract, struct hashmap* subt_upd) {
if (lp->y < 0 || lp->y > 255) return 0;
if (skylight && lp->y <= getHeightMapWorld_guess(world, chunk, lp->x, lp->z)) {
struct block_info* bi = getBlockInfo(getBlockWorld_guess(world, chunk, lp->x, lp->y + 1, lp->z));
if (bi != NULL && bi->lightOpacity <= 16) {
struct world_lightpos lp2;
lp2.x = lp->x;
lp2.y = lp->y + 1;
lp2.z = lp->z;
light_floodfill(world, chunk, &lp2, skylight, subtract, subt_upd);
}
}
struct block_info* bi = getBlockInfo(getBlockWorld_guess(world, chunk, lp->x, lp->y, lp->z));
int lo = bi == NULL ? 1 : bi->lightOpacity;
if (lo < 1) lo = 1;
int le = bi == NULL ? 0 : bi->lightEmission;
int16_t maxl = 0;
uint8_t xpl = getRawLightWorld_guess(world, chunk, lp->x + 1, lp->y, lp->z, !skylight);
uint8_t xnl = getRawLightWorld_guess(world, chunk, lp->x - 1, lp->y, lp->z, !skylight);
uint8_t ypl = getRawLightWorld_guess(world, chunk, lp->x, lp->y + 1, lp->z, !skylight);
uint8_t ynl = getRawLightWorld_guess(world, chunk, lp->x, lp->y - 1, lp->z, !skylight);
uint8_t zpl = getRawLightWorld_guess(world, chunk, lp->x, lp->y, lp->z + 1, !skylight);
uint8_t znl = getRawLightWorld_guess(world, chunk, lp->x, lp->y, lp->z - 1, !skylight);
if (subtract) {
maxl = getRawLightWorld_guess(world, chunk, lp->x, lp->y, lp->z, !skylight) - subtract;
} else {
maxl = xpl;
if (xnl > maxl) maxl = xnl;
if (ypl > maxl) maxl = ypl;
if (ynl > maxl) maxl = ynl;
if (zpl > maxl) maxl = zpl;
if (znl > maxl) maxl = znl;
}
if (!subtract) maxl -= lo;
else maxl += lo;
//printf("%s %i at %i, %i, %i\n", subtract ? "subtract" : "add", maxl, lp->x, lp->y, lp->z);
//if (maxl < 15) {
int ks = 0;
if ((subtract - lo) > 0) {
subtract -= lo;
} else {
ks = 1;
}
//}
int sslf = 0;
if (skylight) {
int hm = getHeightMapWorld_guess(world, chunk, lp->x, lp->z);
if (lp->y >= hm) {
maxl = 15;
if (subtract) sslf = 1;
}
//else maxl = -1;
}
if (maxl < le && !skylight) maxl = le;
if (maxl < 0) maxl = 0;
if (maxl > 15) maxl = 15;
uint8_t pl = getRawLightWorld_guess(world, chunk, lp->x, lp->y, lp->z, !skylight);
if (pl == maxl) return sslf;
setLightWorld_guess(world, chunk, maxl, lp->x, lp->y, lp->z, !skylight);
if (ks) return 1;
if (subtract ? (maxl < 15) : (maxl > 0)) {
if (subtract ? xpl > maxl : xpl < maxl) {
lp->x++;
if (light_floodfill(world, chunk, lp, skylight, subtract, subt_upd) && subtract) {
lp->x--;
void* n = xcopy(lp, sizeof(struct world_lightpos), 0);
put_hashmap(subt_upd, (uint64_t) n, n);
//light_floodfill(world, chunk, lp, skylight, 0, 0);
} else lp->x--;
}
if (subtract ? xnl > maxl : xnl < maxl) {
lp->x--;
if (light_floodfill(world, chunk, lp, skylight, subtract, subt_upd) && subtract) {
lp->x++;
void* n = xcopy(lp, sizeof(struct world_lightpos), 0);
put_hashmap(subt_upd, (uint64_t) n, n);
//light_floodfill(world, chunk, lp, skylight, 0, 0);
} else lp->x++;
}
if (!skylight && (subtract ? ypl > maxl : ypl < maxl)) {
lp->y++;
if (light_floodfill(world, chunk, lp, skylight, subtract, subt_upd) && subtract) {
lp->y--;
//light_floodfill(world, chunk, lp, skylight, 0, 0);
} else lp->y--;
}
if (subtract ? ynl > maxl : ynl < maxl) {
lp->y--;
if (light_floodfill(world, chunk, lp, skylight, subtract, subt_upd) && subtract) {
lp->y++;
void* n = xcopy(lp, sizeof(struct world_lightpos), 0);
put_hashmap(subt_upd, (uint64_t) n, n);
//light_floodfill(world, chunk, lp, skylight, 0, 0);
} else lp->y++;
}
if (subtract ? zpl > maxl : zpl < maxl) {
lp->z++;
if (light_floodfill(world, chunk, lp, skylight, subtract, subt_upd) && subtract) {
lp->z--;
void* n = xcopy(lp, sizeof(struct world_lightpos), 0);
put_hashmap(subt_upd, (uint64_t) n, n);
//light_floodfill(world, chunk, lp, skylight, 0, 0);
} else lp->z--;
}
if (subtract ? znl > maxl : znl < maxl) {
lp->z--;
if (light_floodfill(world, chunk, lp, skylight, subtract, subt_upd) && subtract) {
lp->z++;
void* n = xcopy(lp, sizeof(struct world_lightpos), 0);
put_hashmap(subt_upd, (uint64_t) n, n);
//light_floodfill(world, chunk, lp, skylight, 0, 0);
} else lp->z++;
}
}
return sslf;
}
void setBlockWorld_guess(struct world* world, struct chunk* chunk, block blk, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return;
chunk = getChunk_guess(world, chunk, x >> 4, z >> 4);
if (chunk == NULL) chunk = getChunk(world, x >> 4, z >> 4);
if (chunk == NULL) return;
block ob = getBlockChunk(chunk, x & 0x0f, y, z & 0x0f);
struct block_info* obi = getBlockInfo(ob);
uint16_t ohm = world->dimension == OVERWORLD ? chunk->heightMap[z & 0x0f][x & 0x0f] : 0;
struct world_lightpos lp;
int pchm = 0;
struct hashmap* nup = NULL;
if (obi != NULL) {
if (world->dimension == OVERWORLD && ((y >= ohm && obi->lightOpacity >= 1) || (y < ohm && obi->lightOpacity == 0))) {
pchm = 1;
nup = new_hashmap(1, 0);
lp.x = x;
lp.y = ohm;
lp.z = z;
light_floodfill(world, chunk, &lp, 1, 15, nup); // todo remove nup duplicates
}
}
setBlockChunk(chunk, blk, x & 0x0f, y, z & 0x0f, world->dimension == 0);
uint16_t nhm = world->dimension == OVERWORLD ? chunk->heightMap[z & 0x0f][x & 0x0f] : 0;
BEGIN_BROADCAST_DISTXYZ((double) x + .5, (double) y + .5, (double) z + .5, world->players, CHUNK_VIEW_DISTANCE * 16.)
struct packet* pkt = xmalloc(sizeof(struct packet));
pkt->id = PKT_PLAY_CLIENT_BLOCKCHANGE;
pkt->data.play_client.blockchange.location.x = x;
pkt->data.play_client.blockchange.location.y = y;
pkt->data.play_client.blockchange.location.z = z;
pkt->data.play_client.blockchange.block_id = blk;
add_queue(bc_player->outgoingPacket, pkt);
END_BROADCAST(world->players)
struct block_info* bi = getBlockInfo(ob);
if (bi != NULL && bi->onBlockDestroyed != NULL) (*bi->onBlockDestroyed)(world, ob, x, y, z);
beginProfilerSection("block_update");
updateBlockWorld_guess(world, chunk, x, y, z);
updateBlockWorld_guess(world, chunk, x + 1, y, z);
updateBlockWorld_guess(world, chunk, x - 1, y, z);
updateBlockWorld_guess(world, chunk, x, y, z + 1);
updateBlockWorld_guess(world, chunk, x, y, z - 1);
updateBlockWorld_guess(world, chunk, x, y + 1, z);
updateBlockWorld_guess(world, chunk, x, y - 1, z);
endProfilerSection("block_update");
beginProfilerSection("skylight_update");
struct block_info* nbi = getBlockInfo(blk);
if (nbi == NULL || bi == NULL) return;
if (world->dimension == OVERWORLD) {
if (pchm || bi->lightOpacity != nbi->lightOpacity) {
/*setLightWorld_guess(world, chunk, 15, x, nhm, z, 0);
struct world_lightpos lp;
if (ohm < nhm) {
for (int y = ohm; y < nhm; y++) {
setLightWorld_guess(world, chunk, 0, x, y, z, 0);
}
for (int y = ohm; y < nhm; y++) {
lp.x = x;
lp.y = y;
lp.z = z;
light_floodfill(world, chunk, &lp, 1);
}
} else {
for (int y = nhm; y < ohm; y++) {
setLightWorld_guess(world, chunk, 15, x, y, z, 0);
}
for (int y = nhm; y < ohm; y++) {
lp.x = x;
lp.y = y;
lp.z = z;
light_floodfill(world, chunk, &lp, 1);
}
}
lp.x = x;
lp.y = nhm;
lp.z = z;
light_floodfill(world, chunk, &lp, 1);*/
beginProfilerSection("skylight_rst");
/*for (int32_t lx = x - 16; lx <= x + 16; lx++) {
for (int32_t ly = ohm - 16; ly <= ohm + 16; ly++) {
for (int32_t lz = z - 16; lz <= z + 16; lz++) {
setLightWorld_guess(world, chunk, 0, lx, ly, lz, 0);
}
}
}*/
/*for (int32_t lx = x - 16; lx <= x + 16; lx++) {
for (int32_t lz = z - 16; lz <= z + 16; lz++) {
int16_t hm = getHeightMapWorld_guess(world, chunk, lx, lz);
int16_t lb = ohm - 16;
int16_t ub = ohm + 16;
if (hm < ub && hm > lb) ub = hm;
for (int32_t ly = lb; ly <= ub; ly++) {
if (getRawLightWorld_guess(world, chunk, lx, ly, lz, 0) != 0) setLightWorld_guess(world, chunk, 0, lx, ly, lz, 0);
}
}
}*/
//setLightWorld_guess(world, chunk, 0, x, ohm, z, 0);
if (pchm) { // todo: remove the light before block change
BEGIN_HASHMAP_ITERATION (nup)
struct world_lightpos* nlp = value;
light_floodfill(world, chunk, nlp, 1, 0, 0);
xfree (value);
END_HASHMAP_ITERATION (nup)
del_hashmap(nup);
//light_floodfill(world, chunk, &lp, 1, 0, 0);
setLightWorld_guess(world, chunk, 15, x, nhm, z, 0);
lp.x = x;
lp.y = nhm;
lp.z = z;
light_floodfill(world, chunk, &lp, 1, 0, NULL);
}
lp.x = x;
lp.y = y;
lp.z = z;
light_floodfill(world, chunk, &lp, 1, 0, NULL);
endProfilerSection("skylight_rst");
//TODO: pillar lighting?
/*beginProfilerSection("skylight_set");
for (int32_t lz = z - 16; lz <= z + 16; lz++) {
for (int32_t lx = x - 16; lx <= x + 16; lx++) {
uint16_t hm = getHeightMapWorld_guess(world, chunk, lx, lz);
if (hm > 255) continue;
setLightWorld_guess(world, chunk, 15, lx, hm, lz, 0);
}
}
endProfilerSection("skylight_set");*/
/*beginProfilerSection("skylight_fill");
for (int32_t lz = z - 16; lz <= z + 16; lz++) {
for (int32_t lx = x - 16; lx <= x + 16; lx++) {
uint16_t hm = getHeightMapWorld_guess(world, chunk, lx, lz);
if (hm > 255) continue;
lp.x = lx;
lp.y = hm;
lp.z = lz;
light_floodfill(world, chunk, &lp, 1, 0, 0);
}
}
endProfilerSection("skylight_fill");*/
}
}
endProfilerSection("skylight_update");
beginProfilerSection("blocklight_update");
if (bi->lightEmission != nbi->lightEmission || bi->lightOpacity != nbi->lightOpacity) {
if (bi->lightEmission <= nbi->lightEmission) {
beginProfilerSection("blocklight_update_equals");
struct world_lightpos lp;
lp.x = x;
lp.y = y;
lp.z = z;
light_floodfill(world, chunk, &lp, 0, 0, NULL);
endProfilerSection("blocklight_update_equals");
} else {
beginProfilerSection("blocklight_update_remlight");
nup = new_hashmap(1, 0);
lp.x = x;
lp.y = y;
lp.z = z;
light_floodfill(world, chunk, &lp, 0, bi->lightEmission, nup); // todo remove nup duplicates
BEGIN_HASHMAP_ITERATION (nup)
struct world_lightpos* nlp = value;
light_floodfill(world, chunk, nlp, 0, 0, 0);
xfree (value);
END_HASHMAP_ITERATION (nup)
del_hashmap(nup);
//light_floodfill(world, chunk, &lp, 1, 0, 0);
/*for (int32_t lx = x - 16; lx <= x + 16; lx++) {
for (int32_t ly = y - 16; ly <= y + 16; ly++) {
for (int32_t lz = z - 16; lz <= z + 16; lz++) {
setLightWorld_guess(world, chunk, 0, lx, ly, lz, 1);
}
}
}
for (int32_t lx = x - 16; lx <= x + 16; lx++) {
for (int32_t ly = y - 16; ly <= y + 16; ly++) {
struct world_lightpos lp;
lp.x = lx;
lp.y = ly;
lp.z = z - 16;
light_floodfill(world, chunk, &lp, 0, 0, NULL);
lp.z = z + 16;
light_floodfill(world, chunk, &lp, 0, 0, NULL);
}
}
for (int32_t lz = z - 16; lz <= z + 16; lz++) {
for (int32_t ly = y - 16; ly <= y + 16; ly++) {
struct world_lightpos lp;
lp.x = x - 16;
lp.y = ly;
lp.z = lz;
light_floodfill(world, chunk, &lp, 0, 0, NULL);
lp.x = x + 16;
light_floodfill(world, chunk, &lp, 0, 0, NULL);
}
}
for (int32_t lz = z - 16; lz <= z + 16; lz++) {
for (int32_t lx = x - 16; lx <= x + 16; lx++) {
struct world_lightpos lp;
lp.x = lx;
lp.y = y - 16;
lp.z = lz;
light_floodfill(world, chunk, &lp, 0, 0, NULL);
lp.y = y + 16;
light_floodfill(world, chunk, &lp, 0, 0, NULL);
}
}*/
endProfilerSection("blocklight_update_remlight");
}
}
endProfilerSection("blocklight_update");
}
void setBlockWorld_guess_noupdate(struct world* world, struct chunk* chunk, block blk, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return;
chunk = getChunk_guess(world, chunk, x >> 4, z >> 4);
if (chunk == NULL) return;
block ob = getBlockChunk(chunk, x & 0x0f, y, z & 0x0f);
setBlockChunk(chunk, blk, x & 0x0f, y, z & 0x0f, world->dimension == 0);
BEGIN_BROADCAST_DISTXYZ((double) x + .5, (double) y + .5, (double) z + .5, world->players, CHUNK_VIEW_DISTANCE * 16.)
struct packet* pkt = xmalloc(sizeof(struct packet));
pkt->id = PKT_PLAY_CLIENT_BLOCKCHANGE;
pkt->data.play_client.blockchange.location.x = x;
pkt->data.play_client.blockchange.location.y = y;
pkt->data.play_client.blockchange.location.z = z;
pkt->data.play_client.blockchange.block_id = blk;
add_queue(bc_player->outgoingPacket, pkt);
END_BROADCAST(world->players)
struct block_info* bi = getBlockInfo(ob);
if (bi != NULL && bi->onBlockDestroyed != NULL) (*bi->onBlockDestroyed)(world, ob, x, y, z);
}
struct world* newWorld(size_t chl_count) {
struct world* world = xmalloc(sizeof(struct world));
memset(world, 0, sizeof(struct world));
world->regions = new_hashmap(1, 1);
world->entities = new_hashmap(1, 1);
world->players = new_hashmap(1, 1);
world->chunks = new_hashmap(1, 1);
pthread_mutex_init(&world->tick_mut, NULL);
pthread_cond_init(&world->tick_cond, NULL);
world->chl_count = chl_count;
world->subworlds = new_hashmap(1, 1);
world->skylightSubtracted = 0;
world->scheduledTicks = new_hashmap(1, 1);
return world;
}
int loadWorld(struct world* world, char* path) {
char lp[PATH_MAX];
snprintf(lp, PATH_MAX, "%s/level.dat", path); // could have a double slash, but its okay
int fd = open(lp, O_RDONLY);
if (fd < 0) return -1;
unsigned char* ld = xmalloc(1024);
size_t ldc = 1024;
size_t ldi = 0;
ssize_t i = 0;
while ((i = read(fd, ld + ldi, ldc - ldi)) > 0) {
if (ldc - (ldi += i) < 512) {
ldc += 1024;
ld = xrealloc(ld, ldc);
}
}
close(fd);
if (i < 0) {
xfree(ld);
return -1;
}
unsigned char* nld = NULL;
ssize_t ds = decompressNBT(ld, ldi, (void**) &nld);
xfree(ld);
if (ds < 0) {
return -1;
}
if (readNBT(&world->level, nld, ds) < 0) return -1;
xfree(nld);
struct nbt_tag* data = getNBTChild(world->level, "Data");
world->levelType = getNBTChild(data, "generatorName")->data.nbt_string;
world->spawnpos.x = getNBTChild(data, "SpawnX")->data.nbt_int;
world->spawnpos.y = getNBTChild(data, "SpawnY")->data.nbt_int;
world->spawnpos.z = getNBTChild(data, "SpawnZ")->data.nbt_int;
world->time = getNBTChild(data, "DayTime")->data.nbt_long;
world->age = getNBTChild(data, "Time")->data.nbt_long;
world->lpa = xstrdup(path, 0);
printf("spawn: %i, %i, %i\n", world->spawnpos.x, world->spawnpos.y, world->spawnpos.z);
snprintf(lp, PATH_MAX, "%s/region/", path);
DIR* dir = opendir(lp);
if (dir != NULL) {
struct dirent* de = NULL;
while ((de = readdir(dir)) != NULL) {
if (!endsWith_nocase(de->d_name, ".mca")) continue;
snprintf(lp, PATH_MAX, "%s/region/%s", path, de->d_name);
char* xs = strstr(lp, "/r.") + 3;
char* zs = strchr(xs, '.') + 1;
if (zs == NULL) continue;
struct region* reg = newRegion(lp, strtol(xs, NULL, 10), strtol(zs, NULL, 10), world->chl_count);
uint64_t ri = (((uint64_t)(reg->x) & 0xFFFF) << 16) | (((uint64_t) reg->z) & 0xFFFF);
put_hashmap(world->regions, ri, reg);
}
closedir(dir);
}
return 0;
}
void thr_player_tick(struct subworld* sw) {
while (1) {
pthread_mutex_lock(&sw->world->tick_mut);
pthread_cond_wait(&sw->world->tick_cond, &sw->world->tick_mut);
pthread_mutex_unlock(&sw->world->tick_mut);
if (sw->defunct) {
//assume all players are gone
put_hashmap(sw->world->subworlds, (uint64_t) sw, NULL);
del_hashmap(sw->players);
xfree(sw);
return;
}
beginProfilerSection("player_receive_packet");
BEGIN_HASHMAP_ITERATION(sw->players)
struct player* player = (struct player*) value;
if (player->incomingPacket->size == 0) continue;
pthread_mutex_lock(&player->incomingPacket->data_mutex);
struct packet* wp = pop_nowait_queue(player->incomingPacket);
while (wp != NULL) {
player_receive_packet(player, wp);
freePacket(STATE_PLAY, 0, wp);
xfree(wp);
wp = pop_nowait_queue(player->incomingPacket);
}
pthread_mutex_unlock(&player->incomingPacket->data_mutex);
END_HASHMAP_ITERATION(sw->players)
endProfilerSection("player_receive_packet");
beginProfilerSection("tick_player");
BEGIN_HASHMAP_ITERATION(sw->players)
struct player* player = (struct player*) value;
tick_player(sw->world, player);
tick_entity(sw->world, player->entity); // might need to be moved into separate loop later
END_HASHMAP_ITERATION(sw->players)
endProfilerSection("tick_player");
}
}
void world_pretick(struct world* world) {
if (++world->time >= 24000) world->time = 0;
world->age++;
float pday = ((float) world->time / 24000.) - .25;
if (pday < 0.) pday++;
if (pday > 1.) pday--;
float cel_angle = 1. - ((cosf(pday * M_PI) + 1.) / 2.);
cel_angle = pday + (cel_angle - pday) / 3.;
float psubs = 1. - (cosf(cel_angle * M_PI * 2.) * 2. + .5);
if (psubs < 0.) psubs = 0.;
if (psubs > 1.) psubs = 1.;
//TODO: rain, thunder
world->skylightSubtracted = (uint8_t)(psubs * 11.);
}
void tick_world(struct world* world) {
int32_t lcg = rand();
while (1) {
pthread_mutex_lock (&glob_tick_mut);
pthread_cond_wait(&glob_tick_cond, &glob_tick_mut);
pthread_mutex_unlock(&glob_tick_mut);
world_pretick(world);
pthread_cond_broadcast(&world->tick_cond); // we use a different condition for subtick threads for the pretick
beginProfilerSection("tick_entity");
BEGIN_HASHMAP_ITERATION(world->entities)
struct entity* entity = (struct entity*) value;
if (entity->type != ENT_PLAYER) tick_entity(world, entity);
END_HASHMAP_ITERATION(world->entities)
endProfilerSection("tick_entity");
beginProfilerSection("tick_chunks");
BEGIN_HASHMAP_ITERATION(world->chunks)
struct chunk* chunk = (struct chunk*) value;
beginProfilerSection("tick_chunk_tileentity");
for (size_t x = 0; x < chunk->tileEntitiesTickable->size; x++) {
struct tile_entity* te = (struct tile_entity*) chunk->tileEntitiesTickable->data[x];
if (te == NULL) continue;
(*te->tick)(world, te);
}
endProfilerSection("tick_chunk_tileentity");
beginProfilerSection("tick_chunk_randomticks");
if (RANDOM_TICK_SPEED > 0) for (int t = 0; t < 16; t++) {
struct chunk_section* cs = chunk->sections[t];
if (cs != NULL) {
for (int z = 0; z < RANDOM_TICK_SPEED; z++) {
lcg = lcg * 3 + 1013904223;
int32_t ctotal = lcg >> 2;
uint8_t x = ctotal & 0x0f;
uint8_t z = (ctotal >> 8) & 0x0f;
uint8_t y = (ctotal >> 16) & 0x0f;
block b = getBlockChunk(chunk, x, y + (t << 4), z);
struct block_info* bi = getBlockInfo(b);
if (bi != NULL && bi->randomTick != NULL) (*bi->randomTick)(world, chunk, b, x + (chunk->x << 4), y + (t << 4), z + (chunk->z << 4));
}
}
}
endProfilerSection("tick_chunk_randomticks");
END_HASHMAP_ITERATION(world->chunks)
beginProfilerSection("tick_chunk_scheduledticks");
BEGIN_HASHMAP_ITERATION(world->scheduledTicks)
struct scheduled_tick* st = value;
if (--st->ticksLeft <= 0) {
block b = getBlockWorld(world, st->x, st->y, st->z);
struct block_info* bi = getBlockInfo(b);
int k = 0;
pthread_rwlock_unlock(&world->scheduledTicks->data_mutex);
if (bi->scheduledTick != NULL) k = (*bi->scheduledTick)(world, b, st->x, st->y, st->z);
if (k > 0) {
st->ticksLeft = k;
} else {
put_hashmap(world->scheduledTicks, (uint64_t) st, NULL);
xfree(st);
}
pthread_rwlock_rdlock(&world->scheduledTicks->data_mutex);
}
END_HASHMAP_ITERATION(world->scheduledTicks)
endProfilerSection("tick_chunk_scheduledticks");
endProfilerSection("tick_chunks");
}
}
int saveWorld(struct world* world, char* path) {
return 0;
}
void freeWorld(struct world* world) { // assumes all chunks are unloaded
BEGIN_HASHMAP_ITERATION(world->regions)
freeRegion (value);
END_HASHMAP_ITERATION(world->regions)
//pthread_rwlock_destroy(&world->chl);
del_hashmap(world->regions);
del_hashmap(world->entities);
del_hashmap(world->chunks);
BEGIN_HASHMAP_ITERATION(world->players)
freePlayer(value);
END_HASHMAP_ITERATION(world->players)
del_hashmap(world->players);
BEGIN_HASHMAP_ITERATION(world->scheduledTicks)
xfree(value);
END_HASHMAP_ITERATION(world->scheduledTicks)
del_hashmap(world->scheduledTicks);
if (world->level != NULL) {
freeNBT(world->level);
xfree(world->level);
}
if (world->lpa != NULL) xfree(world->lpa);
xfree(world);
}
struct chunk* getEntityChunk(struct entity* entity) {
return getChunk(entity->world, ((int32_t) entity->x) >> 4, ((int32_t) entity->z) >> 4);
}
void spawnEntity(struct world* world, struct entity* entity) {
entity->world = world;
if (entity->loadingPlayers == NULL) entity->loadingPlayers = new_hashmap(1, 1);
put_hashmap(world->entities, entity->id, entity);
//struct chunk* ch = getEntityChunk(entity);
//if (ch != NULL) {
// put_hashmap(ch->entities, entity->id, entity);
//}
}
void spawnPlayer(struct world* world, struct player* player) {
player->world = world;
if (player->loadedEntities == NULL) player->loadedEntities = new_hashmap(1, 0);
if (player->loadedChunks == NULL) player->loadedChunks = new_hashmap(1, 1);
put_hashmap(world->players, player->entity->id, player);
BEGIN_HASHMAP_ITERATION(world->subworlds)
struct subworld* sw = value;
if (sw->players->entry_count < 100 && !sw->defunct) {
put_hashmap(sw->players, player->entity->id, player);
player->subworld = sw;
BREAK_HASHMAP_ITERATION(world->subworlds)
goto se;
}
END_HASHMAP_ITERATION(world->subworlds)
//no subworld with < 100 players
struct subworld* sw = xmalloc(sizeof(struct subworld));
sw->world = world;
sw->players = new_hashmap(1, 1);
sw->defunct = 0;
player->subworld = sw;
put_hashmap(world->subworlds, (uint64_t) sw, sw);
pthread_t swt;
pthread_create(&swt, NULL, &thr_player_tick, sw);
put_hashmap(sw->players, player->entity->id, player); // no ticks until next tick!
se: ;
spawnEntity(world, player->entity);
}
void despawnPlayer(struct world* world, struct player* player) {
despawnEntity(world, player->entity);
pthread_rwlock_unlock(&player->subworld->players->data_mutex);
put_hashmap(player->subworld->players, player->entity->id, NULL);
pthread_rwlock_rdlock(&player->subworld->players->data_mutex);
BEGIN_HASHMAP_ITERATION(player->loadedEntities)
if (value == NULL || value == player->entity) continue;
struct entity* ent = (struct entity*) value;
put_hashmap(ent->loadingPlayers, player->entity->id, NULL);
END_HASHMAP_ITERATION(player->loadedEntities)
del_hashmap(player->loadedEntities);
player->loadedEntities = NULL;
BEGIN_HASHMAP_ITERATION(player->loadedChunks)
struct chunk* pl = (struct chunk*) value;
if (--pl->playersLoaded <= 0) {
unloadChunk(world, pl);
}
END_HASHMAP_ITERATION(player->loadedChunks)
del_hashmap(player->loadedChunks);
player->loadedChunks = NULL;
put_hashmap(world->players, player->entity->id, NULL);
if (player->subworld->players->entry_count <= 0) player->subworld->defunct = 1;
}
void despawnEntity(struct world* world, struct entity* entity) {
//struct chunk* ch = getEntityChunk(entity);
put_hashmap(world->entities, entity->id, NULL);
//put_hashmap(ch->entities, entity->id, NULL);
BEGIN_BROADCAST(entity->loadingPlayers)
struct packet* pkt = xmalloc(sizeof(struct packet));
pkt->id = PKT_PLAY_CLIENT_DESTROYENTITIES;
pkt->data.play_client.destroyentities.count = 1;
pkt->data.play_client.destroyentities.entity_ids = xmalloc(sizeof(int32_t));
pkt->data.play_client.destroyentities.entity_ids[0] = entity->id;
add_queue(bc_player->outgoingPacket, pkt);
put_hashmap(bc_player->loadedEntities, entity->id, NULL);
END_BROADCAST(entity->loadingPlayers)
del_hashmap(entity->loadingPlayers);
entity->loadingPlayers = NULL;
}
struct entity* getEntity(struct world* world, int32_t id) {
return get_hashmap(world->entities, id);
}
void updateBlockWorld_guess(struct world* world, struct chunk* ch, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return;
block b = getBlockWorld_guess(world, ch, x, y, z);
struct block_info* bi = getBlockInfo(b);
if (bi != NULL && bi->onBlockUpdate != NULL) bi->onBlockUpdate(world, b, x, y, z);
}
void updateBlockWorld(struct world* world, int32_t x, int32_t y, int32_t z) {
if (y < 0 || y > 255) return;
block b = getBlockWorld(world, x, y, z);
struct block_info* bi = getBlockInfo(b);
if (bi != NULL && bi->onBlockUpdate != NULL) bi->onBlockUpdate(world, b, x, y, z);
}