/* * world.c * * Created on: Feb 22, 2016 * Author: root */ #include "light.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct chunk* world_get_chunk(struct world* world, int32_t x, int32_t z) { struct chunk* ch = hashmap_getint(world->chunks, chunk_get_key_direct(x, z)); return ch; } struct chunk* world_get_chunk_guess(struct world* world, struct chunk* ch, int32_t x, int32_t z) { if (ch == NULL) return world_get_chunk(world, x, z); if (ch->x == x && ch->z == z) return ch; if (abs(x - ch->x) > 3 || abs(z - ch->z) > 3) return world_get_chunk(world, x, z); struct chunk* cch = ch; while (cch != NULL) { if (cch->x > x) cch = cch->xn; else if (cch->x < x) cch = cch->xp; if (cch != NULL) { if (cch->z > z) cch = cch->zn; else if (cch->z < z) cch = cch->zp; } if (cch != NULL && cch->x == x && cch->z == z) return cch; } return world_get_chunk(world, x, z); } // WARNING: you almost certainly do not want to call this function. (hence not present in header) // chunks are loaded asynchronously // WARNING: this function should only be called from the chunk loading thread. *it is not thread safe* struct chunk* world_load_chunk(struct world* world, int32_t x, int32_t z) { struct chunk* chunk = hashmap_getint(world->chunks, chunk_get_key_direct(x, z)); if (chunk != NULL) return chunk; int16_t region_x = (int16_t) (x >> 5); int16_t region_z = (int16_t) (z >> 5); uint64_t region_index = (((uint64_t)(region_x) & 0xFFFF) << 16) | (((uint64_t) region_z) & 0xFFFF); struct region* region = hashmap_getint(world->regions, region_index); if (region == NULL) { char path[PATH_MAX]; snprintf(path, PATH_MAX, "%s/region/r.%i.%i.mca", world->world_folder, region_x, region_z); region = region_new(world->pool, path, region_x, region_z); hashmap_putint(world->regions, region_index, region); } chunk = region_load_chunk(region, (int8_t) (x & 0x1F), (int8_t) (z & 0x1F)); if (chunk == NULL) { struct mempool* pool = mempool_new(); pchild(region->pool, pool); chunk = chunk_new(pool, (int16_t) x, (int16_t) z); chunk = worldgen_gen_chunk(world, chunk); } if (chunk != NULL) { chunk->xp = world_get_chunk(world, x + 1, z); if (chunk->xp != NULL) chunk->xp->xn = chunk; chunk->xn = world_get_chunk(world, x - 1, z); if (chunk->xn != NULL) chunk->xn->xp = chunk; chunk->zp = world_get_chunk(world, x, z + 1); if (chunk->zp != NULL) chunk->zp->zn = chunk; chunk->zn = world_get_chunk(world, x, z - 1); if (chunk->zn != NULL) chunk->zn->zp = chunk; hashmap_putint(world->chunks, chunk_get_key(chunk), chunk); return chunk; } return NULL; } void world_chunkload_thread(struct world* world) { //TODO: ensure that on world change that the chunk queue is cleared for a player #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-noreturn" while (1) { struct chunk_request* request = queue_pop(world->chunk_requests); if (request->load) { beginProfilerSection("chunkLoading_getChunk"); // TODO: cache chunk network encodings/compression? // we probably send a chunk a lot more often than it gets mutated struct chunk* chunk = world_get_chunk(world, request->chunk_x, request->chunk_z); if (chunk == NULL) { ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->pre_chunk_loaded != NULL) { struct chunk* new_chunk = plugin->pre_chunk_loaded(world, request->chunk_x, request->chunk_z); if (new_chunk != NULL) { chunk = new_chunk; } } ITER_MAP_END(); } } if (chunk == NULL) { chunk = world_load_chunk(request->world, request->chunk_x, request->chunk_z); } ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->post_chunk_loaded != NULL) { struct chunk* new_chunk = plugin->post_chunk_loaded(world, chunk); if (new_chunk != NULL) { chunk = new_chunk; } } ITER_MAP_END(); } if (request->callback != NULL && chunk != NULL) { request->callback(request->request_arg, world, chunk); } endProfilerSection("chunkLoading_getChunk"); } else { struct chunk* chunk = world_get_chunk(request->world, request->chunk_x, request->chunk_z); ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->pre_chunk_unload != NULL) { if (plugin->pre_chunk_unload(world, chunk)) { chunk = NULL; } } ITER_MAP_END(); } if (chunk != NULL && --chunk->playersLoaded <= 0) { world_unload_chunk(request->world, chunk); } } pprefree(world->chunk_request_pool, request); /* BEGIN_HASHMAP_ITERATION (players) struct player* player = value; if (player->defunct || player->chunksSent >= 5 || player->chunkRequests->size == 0 || (player->conn != NULL && player->conn->writeBuffer_size > 1024 * 1024 * 128)) continue; struct chunk_request* chr = pop_nowait_queue(player->chunkRequests); if (chr == NULL) continue; if (chr->load) { if (chr->world != player->world) { xfree(chr); continue; } player->chunksSent++; if (contains_hashmap(player->loaded_chunks, chunk_get_key_direct(chr->chunk_x, chr->chunk_z))) { xfree(chr); continue; } beginProfilerSection("chunkLoading_getChunk"); struct chunk* ch = world_load_chunk(player->world, chr->chunk_x, chr->chunk_z, b); if (player->loaded_chunks == NULL) { xfree(chr); endProfilerSection("chunkLoading_getChunk"); continue; } endProfilerSection("chunkLoading_getChunk"); if (ch != NULL) { ch->playersLoaded++; //beginProfilerSection("chunkLoading_sendChunk_add"); put_hashmap(player->loaded_chunks, chunk_get_key(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++] = tile_serialize(te, 1); } //endProfilerSection("chunkLoading_sendChunk_tileEntities"); //beginProfilerSection("chunkLoading_sendChunk_dispatch"); add_queue(player->outgoing_packets, pkt); flush_outgoing(player); //endProfilerSection("chunkLoading_sendChunk_dispatch"); } } else { //beginProfilerSection("unchunkLoading"); struct chunk* ch = world_get_chunk(player->world, chr->chunk_x, chr->chunk_z); uint64_t ck = chunk_get_key_direct(chr->chunk_x, chr->chunk_z); if (get_hashmap(player->loaded_chunks, ck) == NULL) { xfree(chr); continue; } put_hashmap(player->loaded_chunks, ck, NULL); if (ch != NULL && !ch->defunct) { if (--ch->playersLoaded <= 0) { world_unload_chunk(player->world, ch); } } if (chr->world == player->world) { struct packet* pkt = xmalloc(sizeof(struct packet)); pkt->id = PKT_PLAY_CLIENT_UNLOADCHUNK; pkt->data.play_client.unloadchunk.chunk_x = chr->chunk_x; pkt->data.play_client.unloadchunk.chunk_z = chr->chunk_z; pkt->data.play_client.unloadchunk.ch = NULL; add_queue(player->outgoing_packets, pkt); flush_outgoing(player); } //endProfilerSection("unchunkLoading"); } xfree(chr); END_HASHMAP_ITERATION (players) */ } #pragma clang diagnostic pop } // WARNING: this function should only be called from the chunk loading thread. *it is not thread safe* void world_unload_chunk(struct world* world, struct chunk* chunk) { //TODO: save chunk pthread_rwlock_wrlock(&world->chunks->rwlock); 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; pthread_rwlock_unlock(&world->chunks->rwlock); hashmap_putint(world->chunks, chunk_get_key(chunk), NULL); pfree(chunk->pool); } int world_get_biome(struct world* world, int32_t x, int32_t z) { struct chunk* chunk = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return 0; return chunk->biomes[z & 0x0f][x & 0x0f]; } uint8_t world_get_light_guess(struct world* world, struct chunk* ch, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return 0; ch = world_get_chunk_guess(world, ch, x >> 4, z >> 4); if (ch != NULL) return chunk_get_light(ch, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f), world->skylightSubtracted); else return world_get_light(world, x, y, z, 0); } uint8_t world_get_raw_light_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 = world_get_chunk_guess(world, ch, x >> 4, z >> 4); if (ch != NULL) return chunk_get_raw_light(ch, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f), blocklight); else return world_get_raw_light(world, x, y, z, blocklight); } uint16_t world_height_guess(struct world* world, struct chunk* ch, int32_t x, int32_t z) { if (world->dimension != OVERWORLD) return 0; ch = world_get_chunk_guess(world, ch, x >> 4, z >> 4); if (ch == NULL) return 0; return ch->heightMap[z & 0x0f][x & 0x0f]; } void world_set_light_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 = world_get_chunk_guess(world, ch, x >> 4, z >> 4); if (ch != NULL) return chunk_set_light(ch, (uint8_t) (light & 0x0f), (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f), blocklight, (uint8_t) (world->dimension == 0)); else return world_set_light(world, (uint8_t) (light & 0x0f), x, y, z, blocklight); } void world_set_light(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 = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return; chunk_set_light(chunk, (uint8_t) (light & 0x0f), (uint8_t) (x & 0x0f), (uint8_t) (y > 255 ? 255 : y), (uint8_t) (z & 0x0f), blocklight, (uint8_t) (world->dimension == 0)); } uint8_t world_get_light(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 = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return 15; if (checkNeighbors) { uint8_t yp = chunk_get_light(chunk, (uint8_t) (x & 0x0f), y, (uint8_t) (z & 0x0f), world->skylightSubtracted); uint8_t xp = world_get_light_guess(world, chunk, x + 1, y, z); uint8_t xn = world_get_light_guess(world, chunk, x - 1, y, z); uint8_t zp = world_get_light_guess(world, chunk, x, y, z + 1); uint8_t zn = world_get_light_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 chunk_get_light(chunk, (uint8_t) (x & 0x0f), (uint8_t) (y > 255 ? 255 : y), (uint8_t) (z & 0x0f), world->skylightSubtracted); } } uint8_t world_get_raw_light(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 = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return 15; return chunk_get_raw_light(chunk, (uint8_t) (x & 0x0f), (uint8_t) (y > 255 ? 255 : y), (uint8_t) (z & 0x0f), blocklight); } block world_get_block(struct world* world, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return 0; struct chunk* chunk = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return 0; return chunk_get_block(chunk, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f)); } block world_get_block_guess(struct world* world, struct chunk* chunk, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return 0; chunk = world_get_chunk_guess(world, chunk, x >> 4, z >> 4); if (chunk != NULL) return chunk_get_block(chunk, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f)); else return world_get_block(world, x, y, z); } void world_explode(struct world* world, struct chunk* ch, double x, double y, double z, float strength) { // TODO: more plugin stuff? ch = world_get_chunk_guess(world, ch, (int32_t) floor(x) >> 4, (int32_t) floor(z) >> 4); for (int32_t j = 0; j < 16; j++) { for (int32_t k = 0; k < 16; k++) { for (int32_t l = 0; l < 16; l++) { if (!(j == 0 || j == 15 || k == 0 || k == 15 || l == 0 || l == 15)) continue; double dx = (double) j / 15.0 * 2.0 - 1.0; double dy = (double) k / 15.0 * 2.0 - 1.0; double dz = (double) l / 15.0 * 2.0 - 1.0; double d = sqrt(dx * dx + dy * dy + dz * dz); dx /= d; dy /= d; dz /= d; float modified_strength = strength * (.7f + game_rand_float() * .6f); double x2 = x; double y2 = y; double z2 = z; for (; modified_strength > 0.; modified_strength -= .225) { int32_t ix = (int32_t) floor(x2); int32_t iy = (int32_t) floor(y2); int32_t iz = (int32_t) floor(z2); block b = world_get_block_guess(world, ch, ix, iy, iz); if (b >> 4 != 0) { struct block_info* bi = getBlockInfo(b); modified_strength -= ((bi->resistance / 5.) + .3) * .3; if (modified_strength > 0.) { //TODO: check if entity will allow destruction block b = world_get_block_guess(world, ch, ix, iy, iz); world_set_block_guess(world, ch, 0, ix, iy, iz); dropBlockDrops(world, b, NULL, ix, iy, iz); //TODO: randomizE? } } x2 += dx * .3; y2 += dy * .3; z2 += dz * .3; } } } } //TODO: knockback & damage } void world_tile_set_tickable(struct world* world, struct tile_entity* tile) { if (tile == NULL || tile->y > 255 || tile->y < 0) return; struct chunk* chunk = world_get_chunk(world, tile->x >> 4, tile->z >> 4); if (chunk == NULL) return; uint64_t key = (uint64_t) (tile->x & 0x0f) << 16 | (uint64_t) tile->y << 8 | (uint64_t) (tile->z & 0x0f); hashmap_putint(chunk->tileEntitiesTickable, key, tile); } void world_tile_unset_tickable(struct world* world, struct tile_entity* tile) { if (tile == NULL || tile->y > 255 || tile->y < 0) return; struct chunk* chunk = world_get_chunk(world, tile->x >> 4, tile->z >> 4); if (chunk == NULL) return; uint64_t key = (uint64_t) (tile->x & 0x0f) << 16 | (uint64_t) tile->y << 8 | (uint64_t) (tile->z & 0x0f); hashmap_putint(chunk->tileEntitiesTickable, key, NULL); } struct tile_entity* world_get_tile(struct world* world, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return NULL; struct chunk* chunk = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return NULL; return chunk_get_tile(chunk, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f)); } void world_set_tile(struct world* world, int32_t x, int32_t y, int32_t z, struct tile_entity* tile) { if (y < 0 || y > 255) return; struct chunk* chunk = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return; chunk_set_tile(chunk, tile, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f)); } int world_set_block(struct world* world, block blk, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return 1; struct chunk* ch = world_get_chunk(world, x >> 4, z >> 4); if (ch == NULL) return 1; return world_set_block_guess(world, ch, blk, x, y, z); } int world_set_block_noupdate(struct world* world, block blk, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return 1; struct chunk* ch = world_get_chunk(world, x >> 4, z >> 4); if (ch == NULL) return 1; return world_set_block_guess_noupdate(world, ch, blk, x, y, z); } void world_schedule_block_tick(struct world* world, int32_t x, int32_t y, int32_t z, int32_t ticksFromNow, float priority) { if (y < 0 || y > 255) return; struct scheduled_tick* tick = pmalloc(world->pool, sizeof(struct scheduled_tick)); tick->x = x; tick->y = y; tick->z = z; tick->ticksLeft = ticksFromNow; tick->priority = priority; tick->src = world_get_block(world, x, y, z); struct encpos pos; pos.x = x; pos.y = y; pos.z = z; hashmap_putint(world->scheduledTicks, *((uint64_t*) &pos), tick); } int32_t world_is_block_tick_scheduled(struct world* world, int32_t x, int32_t y, int32_t z) { struct encpos pos; pos.x = x; pos.y = y; pos.z = z; struct scheduled_tick* tick = hashmap_getint(world->scheduledTicks, *((uint64_t*) &pos)); if (tick == NULL) { return 0; } return tick->ticksLeft; } int world_set_block_guess(struct world* world, struct chunk* chunk, block blk, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return 1; chunk = world_get_chunk_guess(world, chunk, x >> 4, z >> 4); if (chunk == NULL) chunk = world_get_chunk(world, x >> 4, z >> 4); if (chunk == NULL) return 1; block current_block = chunk_get_block(chunk, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f)); struct block_info* current_info = getBlockInfo(current_block); uint16_t heightmap_y = (uint16_t) (world->dimension == OVERWORLD ? chunk->heightMap[z & 0x0f][x & 0x0f] : 0); // struct world_lightpos lp; // int pchm = 0; // struct hashmap* light_updates = NULL; if (current_info != NULL) { int ict = 0; if (current_block != blk) { if (current_info->onBlockDestroyed != NULL) ict = (*current_info->onBlockDestroyed)(world, current_block, x, y, z, blk); if (!ict) { ITER_MAP (plugins) { struct plugin* plugin = value; if (plugin->onBlockDestroyed != NULL && (*plugin->onBlockDestroyed)(world, current_block, x, y, z, blk)) { ict = 1; goto break_map_iter; } ITER_MAP_END(); } break_map_iter:; } if (ict) return 1; } /*if (world->dimension == OVERWORLD && ((y >= heightmap_y && current_info->lightOpacity >= 1) || (y < heightmap_y && current_info->lightOpacity == 0))) { pchm = 1; // light_updates = hashmap_new(8, ) lp.x = x; lp.y = heightmap_y; lp.z = z; light_floodfill(world, chunk, &lp, 1, 15, light_updates); }*/ } restart_block_placed: ; struct block_info* new_info = getBlockInfo(blk); if (new_info != NULL && blk != current_block) { int plugin_cancelled = 0; block obb = blk; if (new_info->onBlockPlaced != NULL) blk = (*new_info->onBlockPlaced)(world, blk, x, y, z, current_block); if (blk == 0 && obb != 0) return 1; else if (blk != obb) goto restart_block_placed; ITER_MAP (plugins) { struct plugin* plugin = value; if (plugin->onBlockPlaced != NULL) { blk = (*plugin->onBlockPlaced)(world, blk, x, y, z, current_block); if (blk == 0 && obb != 0) { return 1; } else if (blk != obb) goto restart_block_placed; } ITER_MAP_END(); } } chunk_set_block(chunk, blk, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f), world->dimension == 0); uint16_t nhm = (uint16_t) (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 = packet_new(mempool_new(), 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; queue_push(bc_player->outgoing_packets, pkt); END_BROADCAST(world->players) beginProfilerSection("block_update"); world_update_block_guess(world, chunk, x, y, z); world_update_block_guess(world, chunk, x + 1, y, z); world_update_block_guess(world, chunk, x - 1, y, z); world_update_block_guess(world, chunk, x, y, z + 1); world_update_block_guess(world, chunk, x, y, z - 1); world_update_block_guess(world, chunk, x, y + 1, z); world_update_block_guess(world, chunk, x, y - 1, z); endProfilerSection("block_update"); beginProfilerSection("skylight_update"); if (new_info == NULL || current_info == NULL) return 0; // if (world->dimension == OVERWORLD) { // if (pchm || current_info->lightOpacity != new_info->lightOpacity) { /*setLightWorld_guess(world, chunk, 15, x, nhm, z, 0); struct world_lightpos lp; if (heightmap_y < nhm) { for (int y = heightmap_y; y < nhm; y++) { setLightWorld_guess(world, chunk, 0, x, y, z, 0); } for (int y = heightmap_y; y < nhm; y++) { lp.x = x; lp.y = y; lp.z = z; light_floodfill(world, chunk, &lp, 1); } } else { for (int y = nhm; y < heightmap_y; y++) { world_set_light_guess(world, chunk, 15, x, y, z, 0); } for (int y = nhm; y < heightmap_y; 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_update(world, chunk, &lp, 1);*/ // beginProfilerSection("skylight_rst"); /*for (int32_t lx = x - 16; lx <= x + 16; lx++) { for (int32_t ly = heightmap_y - 16; ly <= heightmap_y + 16; ly++) { for (int32_t lz = z - 16; lz <= z + 16; lz++) { world_set_light_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 = world_height_guess(world, chunk, lx, lz); int16_t lb = heightmap_y - 16; int16_t ub = heightmap_y + 16; if (hm < ub && hm > lb) ub = hm; for (int32_t ly = lb; ly <= ub; ly++) { if (world_get_raw_light_guess(world, chunk, lx, ly, lz, 0) != 0) world_set_light_guess(world, chunk, 0, lx, ly, lz, 0); } } }*/ //world_set_light_guess(world, chunk, 0, x, heightmap_y, z, 0); /*if (pchm) { // todo: remove the light before block change BEGIN_HASHMAP_ITERATION (light_updates) struct world_lightpos* nlp = value; light_floodfill(world, chunk, nlp, 1, 0, 0); xfree (value); END_HASHMAP_ITERATION (light_updates) del_hashmap(light_updates); //light_update(world, chunk, &lp, 1, 0, 0); world_set_light_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 = world_height_guess(world, chunk, lx, lz); if (hm > 255) continue; world_set_light_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 = world_height_guess(world, chunk, lx, lz); if (hm > 255) continue; lp.x = lx; lp.y = hm; lp.z = lz; light_update(world, chunk, &lp, 1, 0, 0); } } endProfilerSection("skylight_fill");*/ // } // } // endProfilerSection("skylight_update"); beginProfilerSection("blocklight_update"); if (current_info->lightEmission != new_info->lightEmission || current_info->lightOpacity != new_info->lightOpacity) { light_update(world, chunk, x, y, z, 1, new_info->lightEmission); /*if (current_info->lightEmission <= new_info->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"); light_updates = new_hashmap(1, 0); lp.x = x; lp.y = y; lp.z = z; light_floodfill(world, chunk, &lp, 0, current_info->lightEmission, light_updates); // todo remove light_updates duplicates BEGIN_HASHMAP_ITERATION (light_updates) struct world_lightpos* nlp = value; light_floodfill(world, chunk, nlp, 0, 0, 0); xfree (value); END_HASHMAP_ITERATION (light_updates) del_hashmap(light_updates);*/ //light_update(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++) { world_set_light_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_update(world, chunk, &lp, 0, 0, NULL); } }*/ //endProfilerSection("blocklight_update_remlight"); //} } endProfilerSection("blocklight_update"); return 0; } int world_set_block_guess_noupdate(struct world* world, struct chunk* chunk, block requested_block, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return 1; chunk = world_get_chunk_guess(world, chunk, x >> 4, z >> 4); if (chunk == NULL) return 1; block original_block = chunk_get_block(chunk, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (z & 0x0f)); struct block_info* original_info = getBlockInfo(original_block); if (original_info != NULL) { int skip_destroyed = 0; if (original_block != requested_block) { if (original_info->onBlockDestroyed != NULL) { skip_destroyed = (*original_info->onBlockDestroyed)(world, original_block, x, y, z, requested_block); } if (!skip_destroyed) { ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->onBlockDestroyed != NULL && (*plugin->onBlockDestroyed)(world, original_block, x, y, z, requested_block)) { skip_destroyed = 1; goto post_plugin_destroyed; } ITER_MAP_END(); } post_plugin_destroyed:; } if (skip_destroyed) return 1; } } restart_stage_2: ; struct block_info* requested_info = getBlockInfo(requested_block); if (requested_info != NULL && requested_block != original_block) { int skip_placed = 0; block old_block = requested_block; if (requested_info->onBlockPlaced != NULL) requested_block = (*requested_info->onBlockPlaced)(world, requested_block, x, y, z, original_block); if (requested_block == 0 && old_block != 0) skip_placed = 1; else if (requested_block != old_block) goto restart_stage_2; if (!skip_placed) { ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->onBlockPlaced != NULL) { requested_block = (*plugin->onBlockPlaced)(world, requested_block, x, y, z, original_block); if (requested_block == 0 && old_block != 0) { skip_placed = 1; goto post_plugin_place; } else if (requested_block != old_block) goto restart_stage_2; } ITER_MAP_END(); } post_plugin_place:; } if (skip_placed) return 1; } chunk_set_block(chunk, requested_block, (uint8_t) (x & 0x0f), (uint8_t) y, (uint8_t) (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 = packet_new(mempool_new(), 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 = requested_block; queue_push(bc_player->outgoing_packets, pkt); END_BROADCAST(world->players) return 0; } struct world* world_new(struct server* server) { struct mempool* pool = mempool_new(); pchild(server->pool, pool); struct world* world = pcalloc(pool, sizeof(struct world)); world->server = server; world->pool = pool; world->regions = hashmap_thread_new(16, world->pool); world->entities = hashmap_thread_new(64, world->pool); world->players = hashmap_thread_new(32, world->pool); world->chunks = hashmap_thread_new(32, world->pool); world->skylightSubtracted = 0; world->scheduledTicks = hashmap_thread_new(16, world->pool); world->tps = 0; world->ticksInSecond = 0; world->seed = 9876543; perlin_init(&world->perlin, world->seed); world->chunk_request_pool = mempool_new(); pchild(world->pool, world->chunk_request_pool); world->chunk_requests = queue_new(0, 1, world->chunk_request_pool); return world; } int world_load(struct world* world, char* path) { char level_path[PATH_MAX]; snprintf(level_path, PATH_MAX, "%s/level.dat", path); // could have a double slash, but its okay size_t level_length = 0; uint8_t* level_file = read_file_fully(world->pool, level_path, &level_length); if (level_file == NULL) { return -1; } unsigned char* decompressed_level = NULL; ssize_t decompressed_size = nbt_decompress(world->pool, level_file, level_length, (void**) &decompressed_level); pprefree(world->pool, level_file); if (decompressed_size < 0) { return -1; } if (nbt_read(world->pool, &world->level, decompressed_level, (size_t) decompressed_size) < 0) return -1; pprefree(world->pool, decompressed_level); struct nbt_tag* data = nbt_get(world->level, "Data"); world->level_type = nbt_get(data, "generatorName")->data.nbt_string; world->spawnpos.x = nbt_get(data, "SpawnX")->data.nbt_int; world->spawnpos.y = nbt_get(data, "SpawnY")->data.nbt_int; world->spawnpos.z = nbt_get(data, "SpawnZ")->data.nbt_int; world->time = (uint64_t) nbt_get(data, "DayTime")->data.nbt_long; world->age = (uint64_t) nbt_get(data, "Time")->data.nbt_long; world->world_folder = str_dup(path, 0, world->pool); pthread_mutex_init(&world->tick_mut, NULL); phook(world->pool, (void (*)(void*)) pthread_mutex_destroy, &world->tick_mut); pthread_cond_init(&world->tick_cond, NULL); phook(world->pool, (void (*)(void*)) pthread_cond_destroy, &world->tick_cond); acclog(world->server->logger, "spawn: %i, %i, %i", world->spawnpos.x, world->spawnpos.y, world->spawnpos.z); snprintf(level_path, PATH_MAX, "%s/region/", path); DIR* dir = opendir(level_path); if (dir != NULL) { struct dirent* dirent = NULL; while ((dirent = readdir(dir)) != NULL) { if (!str_suffixes(dirent->d_name, ".mca")) continue; snprintf(level_path, PATH_MAX, "%s/region/%s", path, dirent->d_name); char* xs = strstr(level_path, "/r.") + 3; char* zs = strchr(xs, '.') + 1; if (zs == NULL) continue; struct region* reg = region_new(world->pool, level_path, (int16_t) strtol(xs, NULL, 10), (int16_t) strtol(zs, NULL, 10)); uint64_t region_index = (((uint64_t)(reg->x) & 0xFFFF) << 16) | (((uint64_t) reg->z) & 0xFFFF); hashmap_putint(world->regions, region_index, reg); } closedir(dir); } ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->onWorldLoad != NULL) (*plugin->onWorldLoad)(world); ITER_MAP_END() } return 0; } void world_pretick(struct world* world) { if (++world->time >= 24000) world->time = 0; world->age++; float pday = ((float) world->time / 24000.f) - .25f; if (pday < 0.f) pday++; if (pday > 1.f) pday--; float cel_angle = 1.f - ((cosf(pday * (float) M_PI) + 1.f) / 2.f); cel_angle = pday + (cel_angle - pday) / 3.f; float psubs = 1.f - (cosf(cel_angle * (float) M_PI * 2.f) * 2.f + .5f); if (psubs < 0.f) psubs = 0.f; if (psubs > 1.f) psubs = 1.f; //TODO: rain, thunder world->skylightSubtracted = (uint8_t)(psubs * 11.f); } void world_tick(struct world* world) { int32_t lcg = rand(); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-noreturn" while (1) { pthread_mutex_lock(&world->tick_mut); pthread_cond_wait(&world->tick_cond, &world->tick_mut); pthread_mutex_unlock(&world->tick_mut); beginProfilerSection("world_tick"); if (world->tick_counter % 20 == 0) { world->tps = world->ticksInSecond; world->ticksInSecond = 0; } ++world->tick_counter; ++world->ticksInSecond; world_pretick(world); beginProfilerSection("player_receive_packet"); pthread_rwlock_rdlock(&world->players->rwlock); ITER_MAP(world->players) { struct player* player = (struct player*) value; if (player->incoming_packets->size == 0) continue; struct packet* packet = queue_maybepop(player->incoming_packets); while (packet != NULL) { player_receive_packet(player, packet); pfree(packet->pool); packet = queue_maybepop(player->incoming_packets); } ITER_MAP_END(); } pthread_rwlock_unlock(&world->players->rwlock); endProfilerSection("player_receive_packet"); beginProfilerSection("tick_player"); pthread_rwlock_rdlock(&world->players->rwlock); ITER_MAP(world->players) { struct player* player = (struct player*) value; player_tick(player); tick_entity(world, player->entity); // might need to be moved into separate loop later ITER_MAP_END(); } pthread_rwlock_unlock(&world->players->rwlock); endProfilerSection("tick_player"); beginProfilerSection("tick_entity"); pthread_rwlock_rdlock(&world->entities->rwlock); ITER_MAP(world->entities) { struct entity* entity = (struct entity*) value; if (entity->type != ENT_PLAYER) tick_entity(world, entity); if (entity->despawn) { pfree(entity->pool); } ITER_MAP_END(); } pthread_rwlock_unlock(&world->entities->rwlock); endProfilerSection("tick_entity"); beginProfilerSection("tick_chunks"); pthread_rwlock_rdlock(&world->chunks->rwlock); ITER_MAP(world->chunks) { struct chunk* chunk = (struct chunk*) value; beginProfilerSection("tick_chunk_tileentity"); ITER_MAPR(chunk->tileEntitiesTickable, tile_value) { struct tile_entity* tile = tile_value; (*tile->tick)(world, tile); ITER_MAP_END(); } endProfilerSection("tick_chunk_tileentity"); beginProfilerSection("tick_chunk_randomticks"); if (RANDOM_TICK_SPEED > 0){ for (int t = 0; t < 16; t++) { struct chunk_section* section = chunk->sections[t]; if (section != NULL) { for (int z = 0; z < RANDOM_TICK_SPEED; z++) { lcg = lcg * 3 + 1013904223; int32_t ctotal = lcg >> 2; uint8_t x = (uint8_t) (ctotal & 0x0f); uint8_t z = (ctotal >> 8) & 0x0f; uint8_t y = (uint8_t) ((ctotal >> 16) & 0x0f); block b = chunk_get_block(chunk, x, (uint8_t) (y + (t << 4)), z); struct block_info* info = getBlockInfo(b); if (info != NULL && info->randomTick != NULL) { (*info->randomTick)(world, chunk, b, x + (chunk->x << 4), y + (t << 4), z + (chunk->z << 4)); } } } } } endProfilerSection("tick_chunk_randomticks"); ITER_MAP_END(); } pthread_rwlock_unlock(&world->chunks->rwlock); beginProfilerSection("tick_chunk_scheduledticks"); struct mempool* scheduled_tick_pool = mempool_new(); struct prqueue* priority_queue = prqueue_new(scheduled_tick_pool, 0, 0); pthread_rwlock_rdlock(&world->scheduledTicks->rwlock); ITER_MAP(world->scheduledTicks) { struct scheduled_tick* tick = value; if (--tick->ticksLeft <= 0) { // prqueue_add(priority_queue, tick, tick->priority); /*block b = world_get_block(world, tick->x, tick->y, tick->z); struct block_info* bi = getBlockInfo(b); int k = 0; if (bi->scheduledTick != NULL) k = (*bi->scheduledTick)(world, b, tick->x, tick->y, tick->z); if (k > 0) { tick->ticksLeft = k; } else { struct encpos ep; ep.x = tick->x; ep.y = tick->y; ep.z = tick->z; put_hashmap(world->scheduledTicks, *((uint64_t*) &ep), NULL); xfree(tick); }*/ } ITER_MAP_END(); } pthread_rwlock_unlock(&world->scheduledTicks->rwlock); struct scheduled_tick* current_tick = NULL; while ((current_tick = prqueue_pop(priority_queue)) != NULL) { block b = world_get_block(world, current_tick->x, current_tick->y, current_tick->z); if (current_tick->src != b) { struct encpos ep; ep.x = current_tick->x; ep.y = current_tick->y; ep.z = current_tick->z; hashmap_putint(world->scheduledTicks, *((uint64_t*) &ep), NULL); pprefree(world->pool, current_tick); continue; } struct block_info* info = getBlockInfo(b); int k = 0; if (info->scheduledTick != NULL) k = (*info->scheduledTick)(world, b, current_tick->x, current_tick->y, current_tick->z); if (k > 0) { current_tick->ticksLeft = k; current_tick->src = world_get_block(world, current_tick->x, current_tick->y, current_tick->z); } else if (k < 0) { pprefree(world->pool, current_tick); } else { struct encpos ep; ep.x = current_tick->x; ep.y = current_tick->y; ep.z = current_tick->z; hashmap_putint(world->scheduledTicks, *((uint64_t*) &ep), NULL); pprefree(world->pool, current_tick); } } pfree(scheduled_tick_pool); endProfilerSection("tick_chunk_scheduledticks"); endProfilerSection("tick_chunks"); ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->tick_world != NULL) (*plugin->tick_world)(world); ITER_MAP_END(); } endProfilerSection("world_tick"); } #pragma clang diagnostic pop } int world_save(struct world* world, char* path) { return 0; } struct chunk* world_get_entity_chunk(struct entity* entity) { return world_get_chunk(entity->world, ((int32_t) entity->x) >> 4, ((int32_t) entity->z) >> 4); } void world_spawn_entity(struct world* world, struct entity* entity) { entity->world = world; if (entity->loadingPlayers == NULL) { entity->loadingPlayers = hashmap_thread_new(8, entity->pool); } if (entity->attackers == NULL) { entity->attackers = hashmap_new(4, entity->pool); } hashmap_putint(world->entities, entity->id, entity); struct entity_info* info = entity_get_info(entity->type); if (info != NULL) { if (info->initAI != NULL) { entity->ai = pcalloc(entity->pool, sizeof(struct aicontext)); entity->ai->tasks = hashmap_new(8, entity->pool); (*info->initAI)(world, entity); } if (info->onSpawned != NULL) (*info->onSpawned)(world, entity); } ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->onEntitySpawn != NULL) (*plugin->onEntitySpawn)(world, entity); ITER_MAP_END(); } } void world_spawn_player(struct world* world, struct player* player) { player->world = world; if (player->loaded_entities == NULL) { player->loaded_entities = hashmap_new(32, player->pool); } if (player->loaded_chunks == NULL) { player->loaded_chunks = hashmap_thread_new(32, player->pool); } hashmap_putint(world->players, (uint64_t) player->entity->id, player); world_spawn_entity(world, player->entity); ITER_MAP(plugins) { struct plugin* plugin = value; if (plugin->onPlayerSpawn != NULL) (*plugin->onPlayerSpawn)(world, player); ITER_MAP_END(); } } void world_despawn_player(struct world* world, struct player* player) { if (player->open_inventory != NULL) { player_closeWindow(player, (uint16_t) player->open_inventory->window); } world_despawn_entity(world, player->entity); ITER_MAP(player->loaded_entities) { if (value == NULL || value == player->entity) { continue; } struct entity* entity = (struct entity*) value; hashmap_putint(entity->loadingPlayers, (uint64_t) player->entity->id, NULL); ITER_MAP_END(); } pthread_rwlock_wrlock(&player->loaded_chunks->rwlock); ITER_MAP(player->loaded_chunks) { struct chunk* chunk = (struct chunk*) value; if (--chunk->playersLoaded <= 0) { world_unload_chunk(world, chunk); } ITER_MAP_END(); } pthread_rwlock_unlock(&player->loaded_chunks->rwlock); player->loaded_chunks = NULL; hashmap_putint(world->players, (uint64_t) player->entity->id, NULL); } void world_despawn_entity(struct world* world, struct entity* entity) { hashmap_putint(world->entities, (uint64_t) entity->id, NULL); BEGIN_BROADCAST(entity->loadingPlayers) struct packet* pkt = packet_new(mempool_new(), PKT_PLAY_CLIENT_DESTROYENTITIES); pkt->data.play_client.destroyentities.count = 1; pkt->data.play_client.destroyentities.entity_ids = pmalloc(pkt->pool, sizeof(int32_t)); pkt->data.play_client.destroyentities.entity_ids[0] = entity->id; queue_push(bc_player->outgoing_packets, pkt); hashmap_putint(bc_player->loaded_entities, entity->id, NULL); END_BROADCAST(entity->loadingPlayers); entity->loadingPlayers = NULL; ITER_MAP(entity->attackers) { struct entity* attacker = value; if (attacker->attacking == entity) attacker->attacking = NULL; ITER_MAP_END(); } entity->attackers = NULL; entity->attacking = NULL; entity->despawn = 1; //TODO: do we need remove ourselves from entity->attacking->attackers? } struct entity* world_get_entity(struct world* world, int32_t id) { return hashmap_getint(world->entities, (uint64_t) id); } void world_update_block_guess(struct world* world, struct chunk* chunk, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return; block b = world_get_block_guess(world, chunk, x, y, z); struct block_info* bi = getBlockInfo(b); if (bi != NULL && bi->onBlockUpdate != NULL) bi->onBlockUpdate(world, b, x, y, z); } void world_update_block(struct world* world, int32_t x, int32_t y, int32_t z) { if (y < 0 || y > 255) return; block b = world_get_block(world, x, y, z); struct block_info* bi = getBlockInfo(b); if (bi != NULL && bi->onBlockUpdate != NULL) bi->onBlockUpdate(world, b, x, y, z); }