diff --git a/include/tools.h b/include/tools.h index 59d72c2..d5b7a1e 100644 --- a/include/tools.h +++ b/include/tools.h @@ -53,8 +53,10 @@ uint8_t clientSlotToServerSlot (int window_id, uint8_t slot); uint8_t getBlockChange (short x, uint8_t y, short z); void makeBlockChange (short x, uint8_t y, short z, uint8_t block); -uint16_t getMiningResult (PlayerData *player, uint8_t block); uint8_t isInstantlyMined (PlayerData *player, uint8_t block); +uint8_t isColumnBlock (uint8_t block); +uint16_t getMiningResult (uint16_t held_item, uint8_t block); +void bumpToolDurability (PlayerData *player); void handlePlayerAction (PlayerData *player, int action, short x, short y, short z); #endif diff --git a/src/packets.c b/src/packets.c index aa2fc3b..6b96f3d 100644 --- a/src/packets.c +++ b/src/packets.c @@ -509,9 +509,6 @@ int cs_useItemOn (int client_fd) { PlayerData *player; if (getPlayerData(client_fd, &player)) return 1; - // check if the player is in the way - if (x == player->x && (y == player->y || y == player->y + 1) && z == player->z) return 0; - uint16_t *item = &player->inventory_items[player->hotbar]; uint8_t *count = &player->inventory_count[player->hotbar]; uint8_t block = I_to_B(*item); @@ -520,12 +517,22 @@ int cs_useItemOn (int client_fd) { if (block == 0) return 0; // if the selected slot doesn't hold any items, exit if (*count == 0) return 0; - // decrease item amount in selected slot - *count -= 1; - // clear item id in slot if amount is zero - if (*count == 0) *item = 0; - makeBlockChange(x, y, z, block); + // check if the block's placement conditions are met + if ( + !(x == player->x && (y == player->y || y == player->y + 1) && z == player->z) && + (getBlockAt(x, y, z) == B_air) && + (!isColumnBlock(block) || getBlockAt(x, y - 1, z) != B_air) + ) { + // decrease item amount in selected slot + *count -= 1; + // clear item id in slot if amount is zero + if (*count == 0) *item = 0; + // apply server-side block change + makeBlockChange(x, y, z, block); + } + + // sync hotbar contents to player sc_setContainerSlot(client_fd, 0, serverSlotToClientSlot(0, player->hotbar), *count, *item); return 0; diff --git a/src/tools.c b/src/tools.c index 56b1a49..7ff678d 100644 --- a/src/tools.c +++ b/src/tools.c @@ -424,29 +424,12 @@ void makeBlockChange (short x, uint8_t y, short z, uint8_t block) { // Returns the result of mining a block, taking into account the block type and tools // Probability numbers obtained with this formula: N = floor(P * 32 ^ 2) -uint16_t getMiningResult (PlayerData *player, uint8_t block) { - - uint16_t held_item = player->inventory_items[player->hotbar]; - - // In order to avoid storing durability data, items break randomly with - // the probability weighted based on vanilla durability. - uint32_t r = fast_rand(); - if ( - ((held_item == I_wooden_pickaxe || held_item == I_wooden_axe || held_item == I_wooden_shovel) && r < 72796055) || - ((held_item == I_stone_pickaxe || held_item == I_stone_axe || held_item == I_stone_shovel) && r < 32786009) || - ((held_item == I_iron_pickaxe || held_item == I_iron_axe || held_item == I_iron_shovel) && r < 17179869) || - ((held_item == I_golden_pickaxe || held_item == I_golden_axe || held_item == I_golden_shovel) && r < 134217728) || - ((held_item == I_diamond_pickaxe || held_item == I_diamond_axe || held_item == I_diamond_shovel) && r < 2751420) || - ((held_item == I_netherite_pickaxe || held_item == I_netherite_axe || held_item == I_netherite_shovel) && r < 2114705) - ) { - player->inventory_items[player->hotbar] = 0; - player->inventory_count[player->hotbar] = 0; - sc_setContainerSlot(player->client_fd, 0, serverSlotToClientSlot(0, player->hotbar), 0, 0); - } +uint16_t getMiningResult (uint16_t held_item, uint8_t block) { switch (block) { case B_oak_leaves: + uint32_t r = fast_rand(); if (r < 21474836) return I_apple; // 0.5% if (r < 85899345) return I_stick; // 2% if (r < 214748364) return I_oak_sapling; // 5% @@ -480,6 +463,18 @@ uint16_t getMiningResult (PlayerData *player, uint8_t block) { ) return 0; break; + case B_snow: + // Check if player is holding (any) shovel + if ( + held_item != I_wooden_shovel && + held_item != I_stone_shovel && + held_item != I_iron_shovel && + held_item != I_golden_shovel && + held_item != I_diamond_shovel && + held_item != I_netherite_shovel + ) return 0; + break; + default: break; } @@ -487,12 +482,32 @@ uint16_t getMiningResult (PlayerData *player, uint8_t block) { } +// Rolls a random number to determine whether the player's tool should break +void bumpToolDurability (PlayerData *player) { + + uint16_t held_item = player->inventory_items[player->hotbar]; + + // In order to avoid storing durability data, items break randomly with + // the probability weighted based on vanilla durability. + uint32_t r = fast_rand(); + if ( + ((held_item == I_wooden_pickaxe || held_item == I_wooden_axe || held_item == I_wooden_shovel) && r < 72796055) || + ((held_item == I_stone_pickaxe || held_item == I_stone_axe || held_item == I_stone_shovel) && r < 32786009) || + ((held_item == I_iron_pickaxe || held_item == I_iron_axe || held_item == I_iron_shovel) && r < 17179869) || + ((held_item == I_golden_pickaxe || held_item == I_golden_axe || held_item == I_golden_shovel) && r < 134217728) || + ((held_item == I_diamond_pickaxe || held_item == I_diamond_axe || held_item == I_diamond_shovel) && r < 2751420) || + ((held_item == I_netherite_pickaxe || held_item == I_netherite_axe || held_item == I_netherite_shovel) && r < 2114705) + ) { + player->inventory_items[player->hotbar] = 0; + player->inventory_count[player->hotbar] = 0; + sc_setContainerSlot(player->client_fd, 0, serverSlotToClientSlot(0, player->hotbar), 0, 0); + } + +} + // Checks whether the given block would be mined instantly with the held tool uint8_t isInstantlyMined (PlayerData *player, uint8_t block) { - if (block == B_dead_bush) return true; - if (block == B_short_grass) return true; - uint16_t held_item = player->inventory_items[player->hotbar]; if (block == B_snow) return ( @@ -503,7 +518,26 @@ uint8_t isInstantlyMined (PlayerData *player, uint8_t block) { held_item == I_golden_shovel ); - return false; + return ( + block == B_dead_bush || + block == B_short_grass || + block == B_torch + ); + +} + +// Checks whether the given block has to have something beneath it +uint8_t isColumnBlock (uint8_t block) { + + return ( + block == B_snow || + block == B_moss_carpet || + block == B_cactus || + block == B_short_grass || + block == B_dead_bush || + block == B_sand || + block == B_torch + ); } @@ -526,17 +560,25 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short makeBlockChange(x, y, z, 0); - uint16_t item = getMiningResult(player, block); + uint16_t held_item = player->inventory_items[player->hotbar]; + uint16_t item = getMiningResult(held_item, block); if (item) givePlayerItem(player, item, 1); + bumpToolDurability(player); // Check if any blocks above this should break - uint8_t block_above = getBlockAt(x, y + 1, z); - if ( - block_above == B_snow || - block_above == B_moss_carpet || - block_above == B_cactus || - block_above == B_short_grass || - block_above == B_dead_bush - ) return handlePlayerAction(player, 2, x, y + 1, z); + uint8_t y_offset = 1; + uint8_t block_above = getBlockAt(x, y + y_offset, z); + + // Iterate upward over all blocks in the column + 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); + } }