diff --git a/build_registries.js b/build_registries.js index 0761a87..3c95d18 100644 --- a/build_registries.js +++ b/build_registries.js @@ -11,6 +11,7 @@ const blockToItemOverrides = { "redstone_ore": "redstone", "iron_ore": "raw_iron", "coal_ore": "coal", + "copper_ore": "raw_copper", "snow": "snowball", "dead_bush": "stick" }; @@ -47,6 +48,9 @@ const blockWhitelist = [ "water_6", "water_7", "lava", + "lava_2", + "lava_4", + "lava_6", "snowy_grass_block", "mud", "moss_carpet", @@ -54,7 +58,9 @@ const blockWhitelist = [ "stone_slab", "cobblestone_slab", "composter", - "coal_block" + "coal_block", + "copper_ore", + "copper_block" ]; // Currently, only 4 biome types are supported, excluding "beach" @@ -346,7 +352,8 @@ async function convert () { const tagBuffer = serializeTags({ "fluid": { // Water and lava, both flowing and still states - "water": [ 1, 2, 3, 4 ] + "water": [ 1, 2 ], + "lava": [ 3, 4 ] }, "block": { "mineable/pickaxe": [ @@ -362,12 +369,14 @@ async function convert () { itemsAndBlocks.blockRegistry["redstone_ore"], itemsAndBlocks.blockRegistry["iron_ore"], itemsAndBlocks.blockRegistry["coal_ore"], + itemsAndBlocks.blockRegistry["copper_ore"], itemsAndBlocks.blockRegistry["furnace"], itemsAndBlocks.blockRegistry["iron_block"], itemsAndBlocks.blockRegistry["gold_block"], itemsAndBlocks.blockRegistry["diamond_block"], itemsAndBlocks.blockRegistry["redstone_block"], - itemsAndBlocks.blockRegistry["coal_block"] + itemsAndBlocks.blockRegistry["coal_block"], + itemsAndBlocks.blockRegistry["copper_block"] ], "mineable/axe": [ itemsAndBlocks.blockRegistry["oak_log"], diff --git a/src/crafting.c b/src/crafting.c index d9bf627..e6941e7 100644 --- a/src/crafting.c +++ b/src/crafting.c @@ -31,34 +31,14 @@ void getCraftingOutput (PlayerData *player, uint8_t *count, uint16_t *item) { case 1: switch (first_item) { - case I_oak_log: - *item = I_oak_planks; - *count = 4; - return; - case I_oak_planks: - *item = I_oak_button; - *count = 1; - return; - case I_iron_block: - *item = I_iron_ingot; - *count = 9; - return; - case I_gold_block: - *item = I_gold_ingot; - *count = 9; - return; - case I_diamond_block: - *item = I_diamond; - *count = 9; - return; - case I_redstone_block: - *item = I_redstone; - *count = 9; - return; - case I_coal_block: - *item = I_coal; - *count = 9; - return; + case I_oak_log: *item = I_oak_planks; *count = 4; return; + case I_oak_planks: *item = I_oak_button; *count = 1; return; + case I_iron_block: *item = I_iron_ingot; *count = 9; return; + case I_gold_block: *item = I_gold_ingot; *count = 9; return; + case I_diamond_block: *item = I_diamond; *count = 9; return; + case I_redstone_block: *item = I_redstone; *count = 9; return; + case I_coal_block: *item = I_coal; *count = 9; return; + case I_copper_block: *item = I_copper_ingot; *count = 9; return; default: break; } @@ -345,6 +325,7 @@ void getCraftingOutput (PlayerData *player, uint8_t *count, uint16_t *item) { case I_diamond: *item = I_diamond_block; *count = 1; return; case I_redstone: *item = I_redstone_block; *count = 1; return; case I_coal: *item = I_coal_block; *count = 1; return; + case I_copper_ingot: *item = I_copper_block; *count = 1; return; default: break; } break; diff --git a/src/procedures.c b/src/procedures.c index 8afdc87..bfaaaaf 100644 --- a/src/procedures.c +++ b/src/procedures.c @@ -622,7 +622,8 @@ uint8_t isColumnBlock (uint8_t block) { uint8_t isPassableBlock (uint8_t block) { return ( block == B_air || - block == B_water || + (block >= B_water && block < B_water + 8) || + (block >= B_lava && block < B_lava + 4) || block == B_snow || block == B_moss_carpet || block == B_short_grass || @@ -636,6 +637,7 @@ uint8_t isReplaceableBlock (uint8_t block) { return ( block == B_air || (block >= B_water && block < B_water + 8) || + (block >= B_lava && block < B_lava + 4) || block == B_short_grass || block == B_snow ); @@ -869,7 +871,7 @@ 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; - // Query blocks adjacent to this water stream + // Query blocks adjacent to this fluid stream uint8_t adjacent[4] = { getBlockAt(x + 1, y, z), getBlockAt(x - 1, y, z), @@ -877,7 +879,7 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl getBlockAt(x, y, z - 1) }; - // Handle maintaining connections to a water source + // Handle maintaining connections to a fluid source if (level != 0) { // Check if this fluid is connected to a block exactly one level lower uint8_t connected = false; @@ -906,6 +908,7 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl } // Stop flowing laterally at the maximum level + if (level == 3 && fluid == B_lava) return; if (level == 7) return; // Handle lateral water flow, increasing level by 1 @@ -932,7 +935,7 @@ 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 if (block >= B_lava && block < B_lava + 4) fluid = B_lava; else return; handleFluidMovement(x, y, z, fluid, block); @@ -1303,6 +1306,10 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da // Killed by a less than 5 block fall strcpy((char *)recv_buffer + player_name_len, " hit the ground too hard"); recv_buffer[player_name_len + 24] = '\0'; + } else if (damage_type == D_lava) { + // Killed by being in lava + strcpy((char *)recv_buffer + player_name_len, " tried to swim in lava"); + recv_buffer[player_name_len + 22] = '\0'; } else if (attacker_id < -1) { // Killed by a mob strcpy((char *)recv_buffer + player_name_len, " was slain by a mob"); @@ -1314,6 +1321,10 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da strcpy((char *)recv_buffer + player_name_len, " was slain by "); strcpy((char *)recv_buffer + player_name_len + 14, attacker->name); recv_buffer[player_name_len + 14 + strlen(attacker->name)] = '\0'; + } else { + // Unknown death reason + strcpy((char *)recv_buffer + player_name_len, " died"); + recv_buffer[player_name_len + 4] = '\0'; } } else player->health -= effective_damage; @@ -1386,31 +1397,37 @@ void handleServerTick (int64_t time_since_last_tick) { if (player->flags & 0x20) { // Check "client loading" flag // If 3 seconds (60 vanilla ticks) have passed, assume player has loaded player->flagval_16 ++; - if (player->flagval_16 > (unsigned int)(3 * TICKS_PER_SECOND)) { + if (player->flagval_16 > (uint16_t)(3 * TICKS_PER_SECOND)) { player->flags &= ~0x20; player->flagval_16 = 0; } else continue; } - // Send Keep Alive and Update Time packets - sc_keepAlive(player->client_fd); - sc_updateTime(player->client_fd, world_time); // Reset player attack cooldown if (player->flags & 0x01) { - if (player->flagval_8 >= (unsigned int)(0.6f * TICKS_PER_SECOND)) { + if (player->flagval_8 >= (uint8_t)(0.6f * TICKS_PER_SECOND)) { player->flags &= ~0x01; player->flagval_8 = 0; } else player->flagval_8 ++; } // Handle eating animation if (player->flags & 0x10) { - if (player->flagval_16 >= (unsigned int)(1.6f * TICKS_PER_SECOND)) { + if (player->flagval_16 >= (uint16_t)(1.6f * TICKS_PER_SECOND)) { handlePlayerEating(&player_data[i], false); player->flags &= ~0x10; player->flagval_16 = 0; } else player->flagval_16 ++; } - // Heal from saturation if player is able and has enough food + // Below this, process events that happen once per second if (server_ticks % (uint32_t)TICKS_PER_SECOND != 0) continue; + // Send Keep Alive and Update Time packets + sc_keepAlive(player->client_fd); + sc_updateTime(player->client_fd, world_time); + // Tick damage from lava + uint8_t block = getBlockAt(player->x, player->y, player->z); + if (block >= B_lava && block < B_lava + 4) { + hurtEntity(player->client_fd, -1, D_lava, 8); + } + // Heal from saturation if player is able and has enough food if (player->health >= 20 || player->health == 0) continue; if (player->hunger < 18) continue; if (player->saturation >= 600) { @@ -1438,6 +1455,7 @@ void handleServerTick (int64_t time_since_last_tick) { // Tick mob behavior for (int i = 0; i < MAX_MOBS; i ++) { if (mob_data[i].type == 0) continue; + int entity_id = -2 - i; // Handle deallocation on mob death if ((mob_data[i].data & 31) == 0) { @@ -1449,9 +1467,9 @@ void handleServerTick (int64_t time_since_last_tick) { for (int j = 0; j < MAX_PLAYERS; j ++) { if (player_data[j].client_fd == -1) continue; // Spawn death smoke particles - sc_entityEvent(player_data[j].client_fd, -2 - i, 60); + sc_entityEvent(player_data[j].client_fd, entity_id, 60); // Remove the entity from the client - sc_removeEntity(player_data[j].client_fd, -2 - i); + sc_removeEntity(player_data[j].client_fd, entity_id); } continue; } @@ -1465,7 +1483,7 @@ void handleServerTick (int64_t time_since_last_tick) { // Burn hostile mobs if above ground during sunlight if (!passive && (world_time < 13000 || world_time > 23460) && mob_data[i].y > 48) { - hurtEntity(-2 - i, -1, D_on_fire, 2); + hurtEntity(entity_id, -1, D_on_fire, 2); } uint32_t r = fast_rand(); @@ -1518,7 +1536,7 @@ void handleServerTick (int64_t time_since_last_tick) { // If we're already next to the player, hurt them and skip movement if (closest_dist < 3 && abs(mob_data[i].y - closest_player->y) < 2) { - hurtEntity(closest_player->client_fd, -2 - i, D_generic, 6); + hurtEntity(closest_player->client_fd, entity_id, D_generic, 6); continue; } @@ -1557,9 +1575,14 @@ void handleServerTick (int64_t time_since_last_tick) { else continue; } else { uint8_t block_below = getBlockAt(new_x, new_y - 1, new_z); - if (isPassableBlock(block_below)) new_y -= 1; + if (isPassableBlock(block_below) && block != B_water) new_y -= 1; } + if ( // Hurt mobs that stumble into lava + (block >= B_lava && block < B_lava + 4) || + (block_above >= B_lava && block_above < B_lava + 4) + ) hurtEntity(entity_id, -1, D_lava, 8); + // Store new mob position mob_data[i].x = new_x; mob_data[i].y = new_y; @@ -1568,7 +1591,6 @@ void handleServerTick (int64_t time_since_last_tick) { // Broadcast relevant entity movement packets for (int j = 0; j < MAX_PLAYERS; j ++) { if (player_data[j].client_fd == -1) continue; - int entity_id = -2 - i; sc_teleportEntity ( player_data[j].client_fd, entity_id, (double)new_x + 0.5, new_y, (double)new_z + 0.5, diff --git a/src/worldgen.c b/src/worldgen.c index c6d2e12..fbb685e 100644 --- a/src/worldgen.c +++ b/src/worldgen.c @@ -270,25 +270,38 @@ skip_feature: int8_t gap = height - TERRAIN_BASE_HEIGHT; if (y < CAVE_BASE_DEPTH + gap && y > CAVE_BASE_DEPTH - gap) return B_air; - // The chunk-relative X and Z coordinates are used in a bit shift on the hash - // The sum of these is then used to get the Y coordinate of the ore in this column - // This way, each column is guaranteed to have exactly one ore candidate - uint8_t ore_x_component = (anchor.hash >> rx) & 31; - uint8_t ore_z_component = (anchor.hash >> (rz + 16)) & 31; - uint8_t ore_y = ore_x_component + ore_z_component; + // The chunk-relative X and Z coordinates are used as the seed for an + // xorshift RNG/hash function to generate the Y coordinate of the ore + // in this column. This way, each column is guaranteed to have exactly + // one ore candidate, as there will always be a Y value to reference. + uint8_t ore_y = ((rx & 15) << 4) + (rz & 15); + ore_y ^= ore_y << 4; + ore_y ^= ore_y >> 5; + ore_y ^= ore_y << 1; + ore_y &= 63; if (y == ore_y) { - // Since the ore Y coordinate is effectely a random number in range [0;64], - // we use it in another bit shift to get a pseudo-random number for the column - uint8_t ore_probability = (anchor.hash >> ore_y) & 255; + // Since the ore Y coordinate is effectely a random number in range [0;64), + // we use it in a bit shift with the chunk's anchor hash to get another + // pseudo-random number for the ore's rarity. + uint8_t ore_probability = (anchor.hash >> (ore_y % 24)) & 255; // Ore placement is determined by Y level and "probability" - if (y < 15 && ore_probability < 15) return B_diamond_ore; - if (y < 30) { - if (ore_probability < 5) return B_gold_ore; - if (ore_probability < 20) return B_redstone_ore; + if (y < 15) { + if (ore_probability < 10) return B_diamond_ore; + if (ore_probability < 12) return B_gold_ore; + if (ore_probability < 15) return B_redstone_ore; + } + if (y < 30) { + if (ore_probability < 3) return B_gold_ore; + if (ore_probability < 8) return B_redstone_ore; + } + if (y < 54) { + if (ore_probability < 30) return B_iron_ore; + if (ore_probability < 40) return B_copper_ore; } - if (y < 54 && ore_probability < 50) return B_iron_ore; if (ore_probability < 60) return B_coal_ore; + if (y < 5) return B_lava; + return B_cobblestone; } // For everything else, fall back to stone @@ -363,6 +376,8 @@ uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor) { uint8_t getBlockAt (int x, int y, int z) { + if (y < 0) return B_bedrock; + uint8_t block_change = getBlockChange(x, y, z); if (block_change != 0xFF) return block_change;