From 8011a1adfdcb753f310eaa0f5f116df31f21c0af Mon Sep 17 00:00:00 2001 From: p2r3 Date: Sun, 31 Aug 2025 05:30:18 +0300 Subject: [PATCH] implement armor --- src/crafting.c | 80 +++++++++++++++++++++++++++++++--- src/procedures.c | 111 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 180 insertions(+), 11 deletions(-) diff --git a/src/crafting.c b/src/crafting.c index 8be3402..d9bf627 100644 --- a/src/crafting.c +++ b/src/crafting.c @@ -182,6 +182,27 @@ void getCraftingOutput (PlayerData *player, uint8_t *count, uint16_t *item) { return; } break; + // Boot recipes + case I_leather: + case I_iron_ingot: + case I_gold_ingot: + case I_diamond: + case I_netherite_ingot: + if ( + first_col == 0 && first_row < 2 && + player->craft_items[first + 2] == first_item && + player->craft_items[first + 3] == first_item && + player->craft_items[first + 5] == first_item + ) { + if (first_item == I_leather) *item = I_leather_boots; + else if (first_item == I_iron_ingot) *item = I_iron_boots; + else if (first_item == I_gold_ingot) *item = I_golden_boots; + else if (first_item == I_diamond) *item = I_diamond_boots; + else if (first_item == I_netherite_ingot) *item = I_netherite_boots; + *count = 1; + return; + } + break; default: break; } @@ -235,6 +256,27 @@ void getCraftingOutput (PlayerData *player, uint8_t *count, uint16_t *item) { *count = 1; return; } + if ( + first_item == I_oak_planks || + first_item == I_cobblestone + ) break; + case I_leather: + // Helmet recipes + if ( + first_col == 0 && first_row < 2 && + player->craft_items[first + 1] == first_item && + player->craft_items[first + 2] == first_item && + player->craft_items[first + 3] == first_item && + player->craft_items[first + 5] == first_item + ) { + if (first_item == I_leather) *item = I_leather_helmet; + else if (first_item == I_iron_ingot) *item = I_iron_helmet; + else if (first_item == I_gold_ingot) *item = I_golden_helmet; + else if (first_item == I_diamond) *item = I_diamond_helmet; + else if (first_item == I_netherite_ingot) *item = I_netherite_helmet; + *count = 1; + return; + } break; default: break; @@ -242,6 +284,17 @@ void getCraftingOutput (PlayerData *player, uint8_t *count, uint16_t *item) { break; case 7: + // Legging recipes + if (identical && player->craft_items[4] == 0 && player->craft_items[7] == 0) { + switch (first_item) { + case I_leather: *item = I_leather_leggings; *count = 1; return; + case I_iron_ingot: *item = I_iron_leggings; *count = 1; return; + case I_gold_ingot: *item = I_golden_leggings; *count = 1; return; + case I_diamond: *item = I_diamond_leggings; *count = 1; return; + case I_netherite_ingot: *item = I_netherite_leggings; *count = 1; return; + default: break; + } + } switch (first_item) { case I_oak_slab: if ( @@ -260,13 +313,26 @@ void getCraftingOutput (PlayerData *player, uint8_t *count, uint16_t *item) { break; case 8: - if (identical && player->craft_items[first + 4] == 0) { - switch (first_item) { - case I_cobblestone: *item = I_furnace; *count = 1; return; - #ifdef ALLOW_CHESTS - case I_oak_planks: *item = I_chest; *count = 1; return; - #endif - default: break; + if (identical) { + if (player->craft_items[4] == 0) { + // Center slot clear + switch (first_item) { + case I_cobblestone: *item = I_furnace; *count = 1; return; + #ifdef ALLOW_CHESTS + case I_oak_planks: *item = I_chest; *count = 1; return; + #endif + default: break; + } + } else if (player->craft_items[1] == 0) { + // Top-middle slot clear (chestplate recipes) + switch (first_item) { + case I_leather: *item = I_leather_chestplate; *count = 1; return; + case I_iron_ingot: *item = I_iron_chestplate; *count = 1; return; + case I_gold_ingot: *item = I_golden_chestplate; *count = 1; return; + case I_diamond: *item = I_diamond_chestplate; *count = 1; return; + case I_netherite_ingot: *item = I_netherite_chestplate; *count = 1; return; + default: break; + } } } break; diff --git a/src/procedures.c b/src/procedures.c index e341cd4..822f36e 100644 --- a/src/procedures.c +++ b/src/procedures.c @@ -163,7 +163,7 @@ uint8_t serverSlotToClientSlot (int window_id, uint8_t slot) { if (slot < 9) return slot + 36; if (slot >= 9 && slot <= 35) return slot; if (slot == 40) return 45; - if (slot >= 36 && slot <= 39) return 3 - (slot - 36) + 5; + if (slot >= 36 && slot <= 39) return 44 - slot; if (slot >= 41 && slot <= 44) return slot - 40; } else if (window_id == 12) { // crafting table @@ -188,7 +188,7 @@ uint8_t clientSlotToServerSlot (int window_id, uint8_t slot) { if (slot >= 36 && slot <= 44) return slot - 36; if (slot >= 9 && slot <= 35) return slot; if (slot == 45) return 40; - if (slot >= 5 && slot <= 8) return 4 - (slot - 5) + 36; + if (slot >= 5 && slot <= 8) return 44 - slot; // map inventory crafting slots to player data crafting grid (semi-hack) // this abuses the fact that the buffers are adjacent in player data @@ -726,6 +726,86 @@ uint8_t getItemStackSize (uint16_t item) { return 64; } +// Returns defense points for the given piece of armor +// If the input item is not armor, returns 0 +uint8_t getItemDefensePoints (uint16_t item) { + + switch (item) { + case I_leather_helmet: return 1; + case I_golden_helmet: return 2; + case I_iron_helmet: return 2; + case I_diamond_helmet: // Same as netherite + case I_netherite_helmet: return 3; + case I_leather_chestplate: return 3; + case I_golden_chestplate: return 5; + case I_iron_chestplate: return 6; + case I_diamond_chestplate: // Same as netherite + case I_netherite_chestplate: return 8; + case I_leather_leggings: return 2; + case I_golden_leggings: return 3; + case I_iron_leggings: return 5; + case I_diamond_leggings: // Same as netherite + case I_netherite_leggings: return 6; + case I_leather_boots: return 1; + case I_golden_boots: return 1; + case I_iron_boots: return 2; + case I_diamond_boots: // Same as netherite + case I_netherite_boots: return 3; + default: break; + } + + return 0; +} + +// Calculates total defense points for the player's equipped armor +uint8_t getPlayerDefensePoints (PlayerData *player) { + return ( + // Helmet + getItemDefensePoints(player->inventory_items[39]) + + // Chestplate + getItemDefensePoints(player->inventory_items[38]) + + // Leggings + getItemDefensePoints(player->inventory_items[37]) + + // Boots + getItemDefensePoints(player->inventory_items[36]) + ); +} + +// Returns the designated server slot for the given piece of armor +// If input item is not armor, returns 255 +uint8_t getArmorItemSlot (uint16_t item) { + + switch (item) { + case I_leather_helmet: + case I_golden_helmet: + case I_iron_helmet: + case I_diamond_helmet: + case I_netherite_helmet: + return 39; + case I_leather_chestplate: + case I_golden_chestplate: + case I_iron_chestplate: + case I_diamond_chestplate: + case I_netherite_chestplate: + return 38; + case I_leather_leggings: + case I_golden_leggings: + case I_iron_leggings: + case I_diamond_leggings: + case I_netherite_leggings: + return 37; + case I_leather_boots: + case I_golden_boots: + case I_iron_boots: + case I_diamond_boots: + case I_netherite_boots: + return 36; + default: break; + } + + return 255; +} + // Handles the player eating their currently held item // Returns whether the operation was succesful (item was consumed) // If `just_check` is set to true, the item doesn't get consumed @@ -1058,6 +1138,21 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t // Reset eating timer and set eating flag player->flagval_16 = 0; player->flags |= 0x10; + } else if (getItemDefensePoints(*item) != 0) { + // For some reason, this action is sent twice when looking at a block + // Ignore the variant that has coordinates + if (face == 255) return; + // Swap to held piece of armor + uint8_t slot = getArmorItemSlot(*item); + uint16_t prev_item = player->inventory_items[slot]; + player->inventory_items[slot] = *item; + player->inventory_count[slot] = 1; + player->inventory_items[player->hotbar] = prev_item; + player->inventory_count[player->hotbar] = 1; + // Update client inventory + sc_setContainerSlot(player->client_fd, -2, serverSlotToClientSlot(0, slot), 1, *item); + sc_setContainerSlot(player->client_fd, -2, serverSlotToClientSlot(0, player->hotbar), 1, prev_item); + return; } // Don't proceed with block placement if no coordinates were provided @@ -1182,8 +1277,16 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da // Don't continue if the player is already dead if (player->health == 0) return; + // Calculate damage reduction from player's armor + uint8_t defense = getPlayerDefensePoints(player); + // This uses the old (pre-1.9) protection calculation. Factors are + // scaled up 256 times to avoid floating point math. Due to lost + // precision, the 4% reduction factor drops to ~3.9%, although the + // the resulting effective damage is then also rounded down. + uint8_t effective_damage = damage * (256 - defense * 10) / 256; + // Process health change on the server - if (player->health <= damage) { + if (player->health <= effective_damage) { player->health = 0; entity_died = true; @@ -1213,7 +1316,7 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da recv_buffer[player_name_len + 14 + strlen(attacker->name)] = '\0'; } - } else player->health -= damage; + } else player->health -= effective_damage; // Update health on the client sc_setHealth(entity_id, player->health, player->hunger, player->saturation);