├── .nvmrc ├── .prettierignore ├── .eslintignore ├── .npmignore ├── .prettierrc ├── footage.gif ├── src ├── types.ts ├── lib │ ├── isArmor.ts │ ├── equipItem.ts │ └── invUtil.ts ├── data │ └── armor.ts └── index.ts ├── example.js ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── automerge.yml │ └── publish.yml ├── .eslintrc ├── .gitignore ├── tsconfig.json ├── README.md ├── LICENSE └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.16.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | example.js 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | footage.gif 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /footage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrismarineJS/MineflayerArmorManager/HEAD/footage.gif -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import MinecraftData from "minecraft-data"; 2 | 3 | /** Initialized instance of MCData */ 4 | export type IndexedData = ReturnType; 5 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const mineflayer = require("mineflayer"); 2 | const bot = mineflayer.createBot({ 3 | username: "Player", 4 | host: "localhost", 5 | port: 25565, 6 | }); 7 | 8 | bot.loadPlugin(require("./dist")); 9 | 10 | bot.once("spawn", () => bot.armorManager.equipAll()); 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: mineflayer 10 | versions: 11 | - 3.2.0 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "env": { 11 | "node": true 12 | }, 13 | "rules": {} 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Dependency directory 18 | node_modules 19 | 20 | # TS build output directory 21 | dist 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "esModuleInterop": true, 5 | "module": "CommonJS", 6 | "moduleResolution": "node", 7 | "outDir": "dist", 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "ES6" 11 | }, 12 | "include": ["src"], 13 | "exclude": ["node_modules", "**/*.spec.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/isArmor.ts: -------------------------------------------------------------------------------- 1 | import { TypeDestination, offhandMaterials } from "../data/armor"; 2 | import { Item } from "prismarine-item"; 3 | 4 | const armorTypes = Object.keys(TypeDestination); 5 | 6 | export const isArmor = (item: Item): boolean => { 7 | return ( 8 | item && 9 | (armorTypes.some((type) => item.name.endsWith(type)) || 10 | offhandMaterials.some((type) => item.name === type)) 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version-file: ".nvmrc" 18 | cache: "npm" 19 | - run: npm install 20 | - run: npm test 21 | - run: npm run format:check 22 | - run: npm run lint 23 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v1 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Enable auto-merge for Dependabot PRs 19 | run: gh pr merge --auto --rebase "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | -------------------------------------------------------------------------------- /src/data/armor.ts: -------------------------------------------------------------------------------- 1 | /** Mapping of values of which kind of armor goes to which slot */ 2 | export enum TypeDestination { 3 | helmet = "head", 4 | chestplate = "torso", 5 | leggings = "legs", 6 | boots = "feet", 7 | "off-hand" = "off-hand", 8 | } 9 | 10 | export const DESTINATIONS = Object.keys( 11 | TypeDestination, 12 | ) as unknown as (keyof typeof TypeDestination)[]; 13 | 14 | /** Ranked list of armor materials from worst to best */ 15 | export const materials = [ 16 | "leather", 17 | "golden", 18 | "iron", 19 | "chainmail", 20 | "turtle", 21 | "diamond", 22 | "netherite", 23 | ]; 24 | 25 | /** Ranked list of offhand materials from worst to best */ 26 | export const offhandMaterials = ["shield", "totem_of_undying"]; 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArmorManager 2 | 3 | Plugin for [MineFlayer](https://github.com/PrismarineJS/mineflayer), that makes bot automatically equip better armor. 4 | 5 | ![Footage](/footage.gif) 6 | 7 | ## Getting started 8 | 9 | If using **NPM**: 10 | 11 | `npm install mineflayer-armor-manager` 12 | 13 | If using **YARN**: 14 | 15 | `yarn add mineflayer-armor-manager` 16 | 17 | ## Usage 18 | 19 | ```js 20 | const armorManager = require("mineflayer-armor-manager"); 21 | const mineflayer = require("mineflayer"); 22 | 23 | const bot = mineflayer.createBot({ 24 | username: "Player", 25 | host: "localhost", 26 | port: 25565, 27 | }); 28 | 29 | bot.loadPlugin(armorManager); 30 | ``` 31 | 32 | If needed, it's possible to trigger a function that will check whole inventory and equip best possible armor, on spawn for example: 33 | 34 | ```js 35 | bot.once("spawn", () => bot.armorManager.equipAll()); 36 | ``` 37 | 38 | ## License 39 | 40 | MIT © [Konstantin Azizov](https://github.com/G07cha/) 41 | -------------------------------------------------------------------------------- /src/lib/equipItem.ts: -------------------------------------------------------------------------------- 1 | import * as invUtil from "./invUtil"; 2 | import { Bot } from "mineflayer"; 3 | 4 | /** 5 | * Search for item in bot's inventory and equips it 6 | * @return {Boolean} true if item equipped successfully, false if something went wrong 7 | */ 8 | export const equipItem = async (bot: Bot, itemId: number): Promise => { 9 | const item = invUtil.findItemById(bot.inventory, itemId); 10 | const equipped = invUtil.equipped( 11 | bot.inventory, 12 | !bot.supportFeature("doesntHaveOffHandSlot"), 13 | ); 14 | 15 | if (!item) { 16 | return false; 17 | } 18 | 19 | const destination = invUtil.findArmorDestination(item); 20 | const destinationIndex = invUtil.findArmorDestinationIndex(item); 21 | 22 | if (destinationIndex < 0 || !destination) { 23 | return false; 24 | } 25 | 26 | const equippedArmor = equipped[destinationIndex]; 27 | if (!equippedArmor || invUtil.isNewArmorBetter(equippedArmor, item)) { 28 | await bot.equip(item, destination); 29 | return true; 30 | } 31 | 32 | return false; 33 | }; 34 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "mineflayer"; 2 | 3 | import { isArmor } from "./lib/isArmor"; 4 | import { equipItem } from "./lib/equipItem"; 5 | 6 | declare module "mineflayer" { 7 | interface Bot { 8 | armorManager: { 9 | equipAll: () => Promise; 10 | }; 11 | } 12 | } 13 | 14 | const initializeBot: Plugin = (bot) => { 15 | if (!bot) { 16 | throw new Error( 17 | "Bot object is missing, provide mineflayer bot as first argument", 18 | ); 19 | } 20 | 21 | bot.armorManager = { 22 | equipAll: async () => { 23 | for (const item of bot.inventory.items()) { 24 | await equipItem(bot, item.type); 25 | } 26 | }, 27 | }; 28 | 29 | bot.on("playerCollect", (collector, collected) => { 30 | if (collector.username !== bot.username) { 31 | return; 32 | } 33 | const item = collected.getDroppedItem(); 34 | if (item != null && isArmor(item)) { 35 | // Little delay to receive inventory 36 | setTimeout(() => equipItem(bot, item.type), 100); 37 | } 38 | }); 39 | }; 40 | 41 | export = initializeBot; 42 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | push: 4 | branches: 5 | - master # Change this to your default branch 6 | jobs: 7 | npm-publish: 8 | name: npm-publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version-file: ".nvmrc" 15 | cache: "npm" 16 | - run: npm install 17 | - run: npm test 18 | - id: publish 19 | uses: JS-DevTools/npm-publish@v1 20 | with: 21 | token: ${{ secrets.NPM_AUTH_TOKEN }} 22 | - name: Create Release 23 | if: steps.publish.outputs.type != 'none' 24 | id: create_release 25 | uses: actions/create-release@v1 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | with: 29 | tag_name: ${{ steps.publish.outputs.version }} 30 | release_name: Release ${{ steps.publish.outputs.version }} 31 | body: ${{ steps.publish.outputs.version }} 32 | draft: false 33 | prerelease: false 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Konstantin Azizov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mineflayer-armor-manager", 3 | "version": "2.0.1", 4 | "description": "Manage armor automatically with various configs", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "clear": "rm -rf ./dist", 8 | "build": "npm run clear && tsc", 9 | "test": "tsc", 10 | "format": "prettier --list-different --write .", 11 | "format:check": "prettier --check .", 12 | "lint": "eslint ." 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/G07cha/MineflayerArmorManager.git" 17 | }, 18 | "keywords": [ 19 | "mineflayer", 20 | "minecraft", 21 | "armor" 22 | ], 23 | "author": "G07cha", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/G07cha/MineflayerArmorManager/issues" 27 | }, 28 | "engines": { 29 | "node": ">=18" 30 | }, 31 | "homepage": "https://github.com/G07cha/MineflayerArmorManager#readme", 32 | "devDependencies": { 33 | "@typescript-eslint/eslint-plugin": "^8.0.0", 34 | "@typescript-eslint/parser": "^8.0.0", 35 | "eslint": "^9.8.0", 36 | "mocha": "^11.0.1", 37 | "prettier": "^3.0.0", 38 | "prismarine-item": "^1.12.2", 39 | "prismarine-windows": "^2.6.1", 40 | "typescript": "^5.1.6" 41 | }, 42 | "dependencies": { 43 | "minecraft-data": "^3.38.0" 44 | }, 45 | "peerDependencies": { 46 | "mineflayer": "^4.10.0" 47 | }, 48 | "files": [ 49 | "dist/**/*" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/invUtil.ts: -------------------------------------------------------------------------------- 1 | import { StorageEvents } from "mineflayer"; 2 | import { 3 | DESTINATIONS, 4 | TypeDestination, 5 | materials, 6 | offhandMaterials, 7 | } from "../data/armor"; 8 | import { Item } from "prismarine-item"; 9 | import { Window } from "prismarine-windows"; 10 | 11 | export const findItemById = ( 12 | inventory: Window, 13 | itemId: number, 14 | ) => inventory.slots.find((item) => item && item.type === itemId); 15 | 16 | export const findArmorDestinationIndex = (item: Item) => { 17 | let index = DESTINATIONS.findIndex((destination) => 18 | item.name.endsWith(destination), 19 | ); 20 | 21 | if (index < 0) 22 | index = offhandMaterials.some((mat) => item.name === mat) ? 4 : -1; 23 | 24 | return index; 25 | }; 26 | 27 | export const findArmorDestination = (item: Item) => { 28 | let type = DESTINATIONS.find((destination) => 29 | item.name.endsWith(destination), 30 | ); 31 | 32 | if (!type && offhandMaterials.some((mat) => item.name === mat)) 33 | type = "off-hand"; 34 | 35 | return type && TypeDestination[type]; 36 | }; 37 | 38 | const getRank = (item: Item): number => { 39 | const index = materials.findIndex((mat) => item.name.startsWith(mat)); 40 | if (index >= 0) return index; 41 | 42 | return offhandMaterials.findIndex((mat) => item.name === mat); 43 | }; 44 | 45 | export const isNewArmorBetter = (oldArmor: Item, newArmor: Item): boolean => { 46 | const oldArmorRank = getRank(oldArmor); 47 | const newArmorRank = getRank(newArmor); 48 | return newArmorRank > oldArmorRank; 49 | }; 50 | 51 | /** 52 | * Get equipped items(workaround because of https://github.com/PrismarineJS/mineflayer/issues/397) 53 | */ 54 | export const equipped = ( 55 | inventory: Window, 56 | supportsOffhand: boolean, 57 | ): (Item | null)[] => 58 | inventory.slots 59 | .slice(5, 9) 60 | .concat(supportsOffhand ? [inventory.slots[45]] : []); 61 | --------------------------------------------------------------------------------