Commit Diff


commit - 3151f0b6f261ceff3802a7b6da6b3b2d10c3a4fa
commit + 8011a1adfdcb753f310eaa0f5f116df31f21c0af
blob - 8be34020f31f5313efa769993940532b70e3a292
blob + d9bf6274667881d202903ac5a0e2dfc0f0bf6f47
--- src/crafting.c
+++ src/crafting.c
@@ -182,6 +182,27 @@ void getCraftingOutput (PlayerData *player, uint8_t *c
             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 *c
             *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 *c
       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 *c
       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;
blob - e341cd4362eb8db374d2b528086c6dee93389072
blob + 822f36e05d74a4b7691e866911a6eb1b1d3b7e04
--- src/procedures.c
+++ src/procedures.c
@@ -163,7 +163,7 @@ uint8_t serverSlotToClientSlot (int window_id, uint8_t
     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
     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,
     // 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
     // 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
         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);