implement water flow

This commit is contained in:
p2r3
2025-08-26 22:54:48 +03:00
parent aa1334db9e
commit 71737380d5
4 changed files with 120 additions and 8 deletions

View File

@@ -39,6 +39,13 @@ const blockBlacklist = [
const blockWhitelist = [ const blockWhitelist = [
"air", "air",
"water", "water",
"water_1",
"water_2",
"water_3",
"water_4",
"water_5",
"water_6",
"water_7",
"lava", "lava",
"snowy_grass_block", "snowy_grass_block",
"mud", "mud",
@@ -105,6 +112,12 @@ async function extractItemsAndBlocks () {
const snowyState = entry[1].states.find(c => c.properties.snowy); const snowyState = entry[1].states.find(c => c.properties.snowy);
blocks["snowy_" + entry[0].replace("minecraft:", "")] = snowyState.id; blocks["snowy_" + entry[0].replace("minecraft:", "")] = snowyState.id;
} }
// Include levels for fluids
if ("fluid" in entry[1].definition) {
for (let i = 1; i <= 7; i ++) {
blocks[entry[0].replace("minecraft:", "") + "_" + i] = defaultState.id + i;
}
}
} }
for (const item in itemSource) { for (const item in itemSource) {
@@ -332,7 +345,8 @@ async function convert () {
const tagBuffer = serializeTags({ const tagBuffer = serializeTags({
"fluid": { "fluid": {
"water": [ 2 ] // source water block // Water and lava, both flowing and still states
"water": [ 1, 2, 3, 4 ]
}, },
"block": { "block": {
"mineable/pickaxe": [ "mineable/pickaxe": [

View File

@@ -37,6 +37,8 @@ void bumpToolDurability (PlayerData *player);
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z); void handlePlayerAction (PlayerData *player, int action, short x, short y, short z);
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face); void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face);
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block);
void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health); void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage); void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage);
void handleServerTick (int64_t time_since_last_tick); void handleServerTick (int64_t time_since_last_tick);

View File

@@ -29,7 +29,7 @@ uint8_t recv_buffer[256] = {0};
uint32_t world_seed = 0xA103DE6C; uint32_t world_seed = 0xA103DE6C;
uint32_t rng_seed = 0xE2B9419; uint32_t rng_seed = 0xE2B9419;
uint16_t world_time = 13000; uint16_t world_time = 0;
uint16_t client_count; uint16_t client_count;

View File

@@ -488,12 +488,19 @@ uint8_t isPassableBlock (uint8_t block) {
uint8_t isReplaceableBlock (uint8_t block) { uint8_t isReplaceableBlock (uint8_t block) {
return ( return (
block == B_air || block == B_air ||
block == B_water || (block >= B_water && block < B_water + 8) ||
block == B_short_grass || block == B_short_grass ||
block == B_snow block == B_snow
); );
} }
uint8_t isReplaceableFluid (uint8_t block, uint8_t level, uint8_t fluid) {
if (block >= fluid && block - fluid < 8) {
return block - fluid > level;
}
return isReplaceableBlock(block);
}
// Checks whether the given item can be used in a composter // Checks whether the given item can be used in a composter
// Returns the probability (out of 2^32) to return bone meal // Returns the probability (out of 2^32) to return bone meal
uint32_t isCompostItem (uint16_t item) { uint32_t isCompostItem (uint16_t item) {
@@ -627,7 +634,84 @@ uint8_t handlePlayerEating (PlayerData *player, uint8_t just_check) {
return true; return true;
} }
void handlePlayerAction(PlayerData *player, int action, short x, short y, short z) { void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t block) {
// Get fluid level (0-7)
// The terminology here is a bit different from vanilla:
// a higher fluid "level" means the fluid has traveled farther
uint8_t level = block - fluid;
// Query blocks adjacent to this water stream
uint8_t adjacent[4] = {
getBlockAt(x + 1, y, z),
getBlockAt(x - 1, y, z),
getBlockAt(x, y, z + 1),
getBlockAt(x, y, z - 1)
};
// Handle maintaining connections to a water source
if (level != 0) {
// Check if this fluid is connected to a block exactly one level lower
uint8_t connected = false;
for (int i = 0; i < 4; i ++) {
if (adjacent[i] == block - 1) {
connected = true;
break;
}
}
// 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]);
return;
}
}
// Check if water should flow down, prioritize that over lateral flow
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);
}
// Stop flowing laterally at the maximum level
if (level == 7) 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);
}
if (isReplaceableFluid(adjacent[1], level, fluid)) {
makeBlockChange(x - 1, y, z, block + 1);
handleFluidMovement(x - 1, y, z, fluid, block + 1);
}
if (isReplaceableFluid(adjacent[2], level, fluid)) {
makeBlockChange(x, y, z + 1, block + 1);
handleFluidMovement(x, y, z + 1, fluid, block + 1);
}
if (isReplaceableFluid(adjacent[3], level, fluid)) {
makeBlockChange(x, y, z - 1, block + 1);
handleFluidMovement(x, y, z - 1, fluid, block + 1);
}
}
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 + 8) fluid = B_lava;
else return;
handleFluidMovement(x, y, z, fluid, block);
}
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z) {
// Re-sync slot when player drops an item // Re-sync slot when player drops an item
if (action == 3 || action == 4) { if (action == 3 || action == 4) {
@@ -669,11 +753,17 @@ void handlePlayerAction(PlayerData *player, int action, short x, short y, short
if (item) givePlayerItem(player, item, 1); if (item) givePlayerItem(player, item, 1);
bumpToolDurability(player); bumpToolDurability(player);
// Check if any blocks above this should break // Update nearby fluids
uint8_t y_offset = 1; uint8_t block_above = getBlockAt(x, y + 1, z);
uint8_t block_above = getBlockAt(x, y + y_offset, z); 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));
// Iterate upward over all blocks in the column // 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)) { while (isColumnBlock(block_above)) {
// Destroy the next block // Destroy the next block
makeBlockChange(x, y + y_offset, z, 0); makeBlockChange(x, y + y_offset, z, 0);
@@ -781,6 +871,12 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
if (*count == 0) *item = 0; if (*count == 0) *item = 0;
// Apply server-side block change // Apply server-side block change
makeBlockChange(x, y, z, block); makeBlockChange(x, y, z, block);
// Calculate 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));
} }
// Sync hotbar contents to player // Sync hotbar contents to player