|
|
|
@@ -1,3 +1,4 @@
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
@@ -761,6 +762,13 @@ uint8_t isInstantlyMined (PlayerData *player, uint8_t block) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Checks whether the given block can fall down (like sand, anvils, ...)
|
|
|
|
|
uint8_t isFallingBlock (uint8_t block) {
|
|
|
|
|
return (
|
|
|
|
|
block == B_sand
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Checks whether the given block has to have something beneath it
|
|
|
|
|
uint8_t isColumnBlock (uint8_t block) {
|
|
|
|
|
return (
|
|
|
|
@@ -790,8 +798,7 @@ uint8_t isPassableBlock (uint8_t block) {
|
|
|
|
|
}
|
|
|
|
|
// Checks whether the given block is non-solid and spawnable
|
|
|
|
|
uint8_t isPassableSpawnBlock (uint8_t block) {
|
|
|
|
|
if ((block >= B_water && block < B_water + 8) ||
|
|
|
|
|
(block >= B_lava && block < B_lava + 4))
|
|
|
|
|
if (getFluid(block))
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
@@ -1037,6 +1044,17 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
|
|
|
|
|
// a higher fluid "level" means the fluid has traveled farther
|
|
|
|
|
uint8_t level = block - fluid;
|
|
|
|
|
|
|
|
|
|
uint8_t max_level;
|
|
|
|
|
switch (fluid) {
|
|
|
|
|
case B_lava:
|
|
|
|
|
max_level = 3;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case B_water:
|
|
|
|
|
max_level = 7;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Query blocks adjacent to this fluid stream
|
|
|
|
|
uint8_t adjacent[4] = {
|
|
|
|
|
getBlockAt(x + 1, y, z),
|
|
|
|
@@ -1058,10 +1076,7 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
|
|
|
|
|
// If not connected, clear this block and recalculate surrounding flow
|
|
|
|
|
if (!connected) {
|
|
|
|
|
makeBlockChange(x, y, z, B_air);
|
|
|
|
|
checkFluidUpdate(x + 1, y, z, adjacent[0]);
|
|
|
|
|
checkFluidUpdate(x - 1, y, z, adjacent[1]);
|
|
|
|
|
checkFluidUpdate(x, y, z + 1, adjacent[2]);
|
|
|
|
|
checkFluidUpdate(x, y, z - 1, adjacent[3]);
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -1070,42 +1085,38 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
|
|
|
|
|
uint8_t block_below = getBlockAt(x, y - 1, z);
|
|
|
|
|
if (isReplaceableBlock(block_below)) {
|
|
|
|
|
makeBlockChange(x, y - 1, z, fluid);
|
|
|
|
|
return handleFluidMovement(x, y - 1, z, fluid, fluid);
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop flowing laterally at the maximum level
|
|
|
|
|
if (level == 3 && fluid == B_lava) return;
|
|
|
|
|
if (level == 7) return;
|
|
|
|
|
if (level == max_level) return;
|
|
|
|
|
|
|
|
|
|
// Handle lateral water flow, increasing level by 1
|
|
|
|
|
if (isReplaceableFluid(adjacent[0], level, fluid)) {
|
|
|
|
|
makeBlockChange(x + 1, y, z, block + 1);
|
|
|
|
|
handleFluidMovement(x + 1, y, z, fluid, block + 1);
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
}
|
|
|
|
|
if (isReplaceableFluid(adjacent[1], level, fluid)) {
|
|
|
|
|
makeBlockChange(x - 1, y, z, block + 1);
|
|
|
|
|
handleFluidMovement(x - 1, y, z, fluid, block + 1);
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
}
|
|
|
|
|
if (isReplaceableFluid(adjacent[2], level, fluid)) {
|
|
|
|
|
makeBlockChange(x, y, z + 1, block + 1);
|
|
|
|
|
handleFluidMovement(x, y, z + 1, fluid, block + 1);
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
}
|
|
|
|
|
if (isReplaceableFluid(adjacent[3], level, fluid)) {
|
|
|
|
|
makeBlockChange(x, y, z - 1, block + 1);
|
|
|
|
|
handleFluidMovement(x, y, z - 1, fluid, block + 1);
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block) {
|
|
|
|
|
|
|
|
|
|
uint8_t fluid;
|
|
|
|
|
if (block >= B_water && block < B_water + 8) fluid = B_water;
|
|
|
|
|
else if (block >= B_lava && block < B_lava + 4) fluid = B_lava;
|
|
|
|
|
else return;
|
|
|
|
|
|
|
|
|
|
handleFluidMovement(x, y, z, fluid, block);
|
|
|
|
|
|
|
|
|
|
uint8_t getFluid(uint8_t block) {
|
|
|
|
|
if (block >= B_water && block < B_water + 8)
|
|
|
|
|
return B_water;
|
|
|
|
|
else if (block >= B_lava && block < B_lava + 4)
|
|
|
|
|
return B_lava;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef ENABLE_PICKUP_ANIMATION
|
|
|
|
@@ -1143,6 +1154,111 @@ void playPickupAnimation (PlayerData *player, uint16_t item, double x, double y,
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// can not be zero!!
|
|
|
|
|
#define SAND_FALL_T 15
|
|
|
|
|
#define SAND_FALL_PROPAGATE_T 2
|
|
|
|
|
|
|
|
|
|
void processBlockUpdate (short x, uint8_t y, short z, uint8_t block, short update_kind) {
|
|
|
|
|
if (update_kind & UPDATE_BASIC) {
|
|
|
|
|
// "normal" block update
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update_kind & UPDATE_CHECK_WATER) {
|
|
|
|
|
uint8_t fluid = getFluid(block);
|
|
|
|
|
if (fluid) {
|
|
|
|
|
uint8_t flow_speed;
|
|
|
|
|
switch (fluid) {
|
|
|
|
|
case B_lava:
|
|
|
|
|
flow_speed = 30;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case B_water:
|
|
|
|
|
flow_speed = 5;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deferBlockUpdate(x, y, z, flow_speed, UPDATE_FLOW_WATER);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update_kind & UPDATE_FLOW_WATER) {
|
|
|
|
|
uint8_t fluid = getFluid(block);
|
|
|
|
|
if (fluid) {
|
|
|
|
|
handleFluidMovement(x, y, z, fluid, block);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update_kind & UPDATE_CHECK_SAND_FALL) {
|
|
|
|
|
// we have a separate UPDATE_CHECK_SAND_FALL,
|
|
|
|
|
// to make chains of falling sand fall as one,
|
|
|
|
|
// with less delay between each
|
|
|
|
|
|
|
|
|
|
if (isFallingBlock(block) && y) {
|
|
|
|
|
uint8_t below = getBlockAt(x, y - 1, z);
|
|
|
|
|
if (isReplaceableBlock(below)) {
|
|
|
|
|
// move the sand down in 15 ticks
|
|
|
|
|
deferBlockUpdate(x, y, z, SAND_FALL_T - 1, UPDATE_FALL_SAND);
|
|
|
|
|
|
|
|
|
|
if (y != 255 && isFallingBlock(getBlockAt(x, y + 1, z))) {
|
|
|
|
|
// also tell the block above that the sand will be gone soon
|
|
|
|
|
deferBlockUpdate(x, y + 1, z, SAND_FALL_PROPAGATE_T, UPDATE_CHECK_SAND_FALL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update_kind & UPDATE_FALL_SAND) {
|
|
|
|
|
// make sure that is a fallable:tm: block, and we are not in the floor
|
|
|
|
|
if (isFallingBlock(block) && y) {
|
|
|
|
|
uint8_t below = getBlockAt(x, y - 1, z);
|
|
|
|
|
// TODO: what to do if below block breaks sand
|
|
|
|
|
if (isReplaceableBlock(below)) {
|
|
|
|
|
// TODO: drop item of below block
|
|
|
|
|
makeBlockChange(x, y, z, B_air);
|
|
|
|
|
makeBlockChange(x, y - 1, z, B_air);
|
|
|
|
|
makeBlockChange(x, y - 1, z, block);
|
|
|
|
|
// update this block after moved
|
|
|
|
|
processBlockUpdate (x, y - 1, z, block, UPDATE_NOW);
|
|
|
|
|
// also update the neighbors
|
|
|
|
|
updateXZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
updateXZNeighbors(x, y - 1, z, UPDATE_NOW);
|
|
|
|
|
if (y != 255) {
|
|
|
|
|
processBlockUpdate(x, y + 1, z, getBlockAt(x, y + 1, z), UPDATE_NOW);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void updateXZNeighbors (short x, uint8_t y, short z, short update_kind) {
|
|
|
|
|
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z), update_kind);
|
|
|
|
|
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z), update_kind);
|
|
|
|
|
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1), update_kind);
|
|
|
|
|
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1), update_kind);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void updateXYZNeighbors (short x, uint8_t y, short z, short update_kind) {
|
|
|
|
|
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z), update_kind);
|
|
|
|
|
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z), update_kind);
|
|
|
|
|
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1), update_kind);
|
|
|
|
|
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1), update_kind);
|
|
|
|
|
processBlockUpdate(x, y - 1, z, getBlockAt(x, y - 1, z), update_kind);
|
|
|
|
|
processBlockUpdate(x, y + 1, z, getBlockAt(x, y + 1, z), update_kind);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void deferBlockUpdate (short x, uint8_t y, short z, uint8_t await_ticks, short update_kind) {
|
|
|
|
|
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeferredBlockUpdate *u = &deferred_block_updates[deferred_block_updates_count ++];
|
|
|
|
|
u->x = x;
|
|
|
|
|
u->y = y;
|
|
|
|
|
u->z = z;
|
|
|
|
|
u->await_ticks = await_ticks + is_processing_deferred_block_updates;
|
|
|
|
|
u->update_kind = update_kind;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z) {
|
|
|
|
|
|
|
|
|
|
// Re-sync slot when player drops an item
|
|
|
|
@@ -1166,55 +1282,34 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short
|
|
|
|
|
// Ignore further actions not pertaining to mining blocks
|
|
|
|
|
if (action != 0 && action != 2) return;
|
|
|
|
|
|
|
|
|
|
uint8_t block = getBlockAt(x, y, z);
|
|
|
|
|
|
|
|
|
|
// In creative, only the "start mining" action is sent
|
|
|
|
|
// No additional verification is performed, the block is simply removed
|
|
|
|
|
if (action == 0 && GAMEMODE == 1) {
|
|
|
|
|
makeBlockChange(x, y, z, 0);
|
|
|
|
|
return;
|
|
|
|
|
makeBlockChange(x, y, z, B_air);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// If this is a "start mining" packet, the block must be instamine
|
|
|
|
|
if (action == 0 && !isInstantlyMined(player, block)) return;
|
|
|
|
|
|
|
|
|
|
// Don't continue if the block change failed
|
|
|
|
|
if (makeBlockChange(x, y, z, B_air)) return;
|
|
|
|
|
|
|
|
|
|
uint16_t held_item = player->inventory_items[player->hotbar];
|
|
|
|
|
uint16_t item = getMiningResult(held_item, block);
|
|
|
|
|
bumpToolDurability(player);
|
|
|
|
|
|
|
|
|
|
if (item) {
|
|
|
|
|
#ifdef ENABLE_PICKUP_ANIMATION
|
|
|
|
|
playPickupAnimation(player, item, x, y, z);
|
|
|
|
|
#endif
|
|
|
|
|
givePlayerItem(player, item, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t block = getBlockAt(x, y, z);
|
|
|
|
|
|
|
|
|
|
// If this is a "start mining" packet, the block must be instamine
|
|
|
|
|
if (action == 0 && !isInstantlyMined(player, block)) return;
|
|
|
|
|
|
|
|
|
|
// Don't continue if the block change failed
|
|
|
|
|
if (makeBlockChange(x, y, z, 0)) return;
|
|
|
|
|
|
|
|
|
|
uint16_t held_item = player->inventory_items[player->hotbar];
|
|
|
|
|
uint16_t item = getMiningResult(held_item, block);
|
|
|
|
|
bumpToolDurability(player);
|
|
|
|
|
|
|
|
|
|
if (item) {
|
|
|
|
|
#ifdef ENABLE_PICKUP_ANIMATION
|
|
|
|
|
playPickupAnimation(player, item, x, y, z);
|
|
|
|
|
#endif
|
|
|
|
|
givePlayerItem(player, item, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update nearby fluids
|
|
|
|
|
uint8_t block_above = getBlockAt(x, y + 1, z);
|
|
|
|
|
#ifdef DO_FLUID_FLOW
|
|
|
|
|
checkFluidUpdate(x, y + 1, z, block_above);
|
|
|
|
|
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
|
|
|
|
|
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
|
|
|
|
|
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
|
|
|
|
|
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Check if any blocks above this should break, and if so,
|
|
|
|
|
// iterate upward over all blocks in the column and break them
|
|
|
|
|
uint8_t y_offset = 1;
|
|
|
|
|
while (isColumnBlock(block_above)) {
|
|
|
|
|
// Destroy the next block
|
|
|
|
|
makeBlockChange(x, y + y_offset, z, 0);
|
|
|
|
|
// Check for item drops *without a tool*
|
|
|
|
|
uint16_t item = getMiningResult(0, block_above);
|
|
|
|
|
if (item) givePlayerItem(player, item, 1);
|
|
|
|
|
// Select the next block in the column
|
|
|
|
|
y_offset ++;
|
|
|
|
|
block_above = getBlockAt(x, y + y_offset, z);
|
|
|
|
|
}
|
|
|
|
|
// Update nearby blocks
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face) {
|
|
|
|
@@ -1350,8 +1445,7 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
|
|
|
|
|
(y == player->y || y == player->y + 1) &&
|
|
|
|
|
z == player->z
|
|
|
|
|
) &&
|
|
|
|
|
isReplaceableBlock(getBlockAt(x, y, z)) &&
|
|
|
|
|
(!isColumnBlock(block) || getBlockAt(x, y - 1, z) != B_air)
|
|
|
|
|
isReplaceableBlock(getBlockAt(x, y, z))
|
|
|
|
|
) {
|
|
|
|
|
// Apply server-side block change
|
|
|
|
|
if (makeBlockChange(x, y, z, block)) return;
|
|
|
|
@@ -1359,14 +1453,9 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
|
|
|
|
|
*count -= 1;
|
|
|
|
|
// Clear item id in slot if amount is zero
|
|
|
|
|
if (*count == 0) *item = 0;
|
|
|
|
|
// Calculate fluid flow
|
|
|
|
|
#ifdef DO_FLUID_FLOW
|
|
|
|
|
checkFluidUpdate(x, y + 1, z, getBlockAt(x, y + 1, z));
|
|
|
|
|
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
|
|
|
|
|
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
|
|
|
|
|
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
|
|
|
|
|
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
|
|
|
|
|
#endif
|
|
|
|
|
// Send updates
|
|
|
|
|
processBlockUpdate(x, y, z, block, UPDATE_NOW);
|
|
|
|
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sync hotbar contents to player
|
|
|
|
@@ -1685,6 +1774,22 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|
|
|
|
*/
|
|
|
|
|
if (rng_seed == 0) rng_seed = world_seed;
|
|
|
|
|
|
|
|
|
|
// block updates might add more deferred block updates,
|
|
|
|
|
// so we temporarily make all new block updates add one more tick to the defer tick counter
|
|
|
|
|
is_processing_deferred_block_updates = 1;
|
|
|
|
|
int next_update_idx = 0;
|
|
|
|
|
for (int i = 0; i < deferred_block_updates_count; i ++) {
|
|
|
|
|
DeferredBlockUpdate *u = &deferred_block_updates[i];
|
|
|
|
|
if (u->await_ticks) {
|
|
|
|
|
u->await_ticks --;
|
|
|
|
|
deferred_block_updates[next_update_idx ++] = *u;
|
|
|
|
|
} else {
|
|
|
|
|
processBlockUpdate(u->x, u->y, u->z, getBlockAt(u->x, u->y, u->z), u->update_kind);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
deferred_block_updates_count = next_update_idx;
|
|
|
|
|
is_processing_deferred_block_updates = 0;
|
|
|
|
|
|
|
|
|
|
// Tick mob behavior
|
|
|
|
|
for (int i = 0; i < MAX_MOBS; i ++) {
|
|
|
|
|
if (mob_data[i].type == 0) continue;
|
|
|
|
|