basin/src/world.c

1129 lines
49 KiB
C

/*
* world.c
*
* Created on: Feb 22, 2016
* Author: root
*/
#include "light.h"
#include <basin/network.h>
#include <basin/packet.h>
#include <basin/game.h>
#include <basin/block.h>
#include <basin/tileentity.h>
#include <basin/entity.h>
#include <basin/world.h>
#include <basin/globals.h>
#include <basin/profile.h>
#include <basin/server.h>
#include <basin/ai.h>
#include <basin/plugin.h>
#include <basin/perlin.h>
#include <basin/biome.h>
#include <basin/entity.h>
#include <basin/worldgen.h>
#include <basin/player.h>
#include <basin/nbt.h>
#include <basin/worldmanager.h>
#include <avuna/prqueue.h>
#include <avuna/string.h>
#include <avuna/pmem.h>
#include <avuna/queue.h>
#include <avuna/hash.h>
#include <avuna/util.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <linux/limits.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <zlib.h>
#include <dirent.h>
#include <math.h>
#include <errno.h>
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);
}